From f5898d76c5ebc43bab7d4d6787788975c59846ab Mon Sep 17 00:00:00 2001 From: 0xumarkhatab Date: Mon, 17 Mar 2025 10:15:03 +0500 Subject: [PATCH 1/5] added updated code from monorepo --- configs/deploy-config.toml | 19 +- deploy.sh | 17 + .../batchDeploymentScript/BatchDeployers.sol | 6 +- .../SystemConfigManager.sol | 12 +- .../batchDeploymentScript/batchDeploy.s.sol | 24 +- scripts/deploy.s.sol | 420 ------------------ simulate_deploy.sh | 17 + src/LendingPool.sol | 12 +- src/LendingPoolStorage.sol | 2 +- src/RVaultAsset.sol | 73 ++- src/Router.sol | 2 +- .../LendingPoolAddressesProvider.sol | 5 +- src/interfaces/IRVaultAsset.sol | 2 + .../helpers/layerzero/OAppOptionsType3.sol | 21 +- src/tokenization/RToken.sol | 29 +- test/Base.t.sol | 11 +- test/LendingPool/LendingPoolTestDeposit.t.sol | 12 +- .../LendingPool/LendingPoolTestWithdraw.t.sol | 31 +- test/utils/TestERC20.sol | 36 +- 19 files changed, 243 insertions(+), 508 deletions(-) create mode 100644 deploy.sh delete mode 100644 scripts/deploy.s.sol create mode 100644 simulate_deploy.sh diff --git a/configs/deploy-config.toml b/configs/deploy-config.toml index cf2abe18..4fc21cd8 100644 --- a/configs/deploy-config.toml +++ b/configs/deploy-config.toml @@ -1,7 +1,7 @@ [deploy_config] chains = ["eth_mainnet","op_mainnet","sepolia-eth"] [owner] -address = "0x34b13101802b97cbfbC33a4eb597017dd20dd31C" +address = "0x5377679614bc0BB997d82D11D79A87e3c5695848" [underlying] # riftlendUnderlying @@ -49,20 +49,20 @@ lpType="OpSuperchain_LENDING_POOL" salt = "riftlendProxyAdmin" [pool_admin] -address="0x34b13101802b97cbfbC33a4eb597017dd20dd31C" +address="0x5377679614bc0BB997d82D11D79A87e3c5695848" [lending_pool] # riftlendLendingPool -salt = "riftlendLendingPool" +salt = "riftlendLendingPool1" [lending_pool_configurator] # riftlendLpConfigurator -salt = "riftlendLpConfigurator" +salt = "riftlendLpConfigurator1" [lending_pool_collateral_manager] # riftlendLpCollateralManager -salt = "riftlendLpCollateralManager" +salt = "riftlendLpCollateralManager1" [default_reserve_interest_rate_strategy] # riftlendDefaultStrategy @@ -78,7 +78,7 @@ stableRateSlope2 = 5 [price_oracle] # riftlendPriceOracle salt = "riftlendPriceOracle" -owner_address = "0x34b13101802b97cbfbC33a4eb597017dd20dd31C" +owner_address = "0x5377679614bc0BB997d82D11D79A87e3c5695848" [event_validator] # riftlendEventValidator @@ -94,7 +94,7 @@ address6="0x2EEB89646C0fC522980f08a6DF6f9843fefFBA77" total=6 [treasury] -address = "0x34b13101802b97cbfbC33a4eb597017dd20dd31C" +address = "0x5377679614bc0BB997d82D11D79A87e3c5695848" [incentives_controller] address = "0x0000000000000000000000000000000000000000" @@ -159,3 +159,8 @@ chain_test_e_cross_l2_prover_address = "0xcDa03d74DEc5B24071D1799899B2e0653C24e5 chain_test_e_weth = "0xb16f35c0ae2912430dac15764477e179d9b9ebea" chain_test_e_lz_endpoint_v2 = "0x6EDCE65403992e310A62460808c4b910D972f10f" chain_test_e_lz_delegate="0xf9E5bFE615853BC0ce310Dd3187277B0bbAC112c" + +[lz_eids_config] + +chains=[11155420,84532,1301,421614,11155111] +eids=[40232,40245,40333,40231,40161] diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 00000000..bb6ac271 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo "Starting deployments across multiple chains..." + +echo "Deploying to Optimism Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://opt-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --broadcast --sender 0x5377679614bc0BB997d82D11D79A87e3c5695848 + +echo "Deploying to Arbitrum Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://arb-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --broadcast --sender 0x5377679614bc0BB997d82D11D79A87e3c5695848 + +echo "Deploying to Base Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://base-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --broadcast --sender 0x5377679614bc0BB997d82D11D79A87e3c5695848 + +echo "Deploying to Unichain Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://unichain-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --broadcast --sender 0x5377679614bc0BB997d82D11D79A87e3c5695848 + +echo "All deployments completed!" \ No newline at end of file diff --git a/scripts/batchDeploymentScript/BatchDeployers.sol b/scripts/batchDeploymentScript/BatchDeployers.sol index 62339ac5..3721ed34 100644 --- a/scripts/batchDeploymentScript/BatchDeployers.sol +++ b/scripts/batchDeploymentScript/BatchDeployers.sol @@ -143,12 +143,10 @@ contract BatchDeployer2 { address public lendingPoolImpl; address public lendingPoolConfigurator; - constructor( - string memory lpSalt, - string memory lpConfiguratorSalt - ) { + constructor(string memory lpSalt, string memory lpConfiguratorSalt) { lendingPoolImpl = Create2Helper.deployContractWithArgs("LendingPool", lpSalt, type(LendingPool).creationCode, ""); + lendingPoolConfigurator = Create2Helper.deployContractWithArgs( "LendingPoolConfigurator", lpConfiguratorSalt, type(LendingPoolConfigurator).creationCode, "" ); diff --git a/scripts/batchDeploymentScript/SystemConfigManager.sol b/scripts/batchDeploymentScript/SystemConfigManager.sol index a1541c22..fd406b9d 100644 --- a/scripts/batchDeploymentScript/SystemConfigManager.sol +++ b/scripts/batchDeploymentScript/SystemConfigManager.sol @@ -20,15 +20,14 @@ import {EventValidator} from "src/libraries/EventValidator.sol"; import {console} from "forge-std/Script.sol"; contract SystemConfigManager is Initializable { - address owner; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* Constructor */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - - constructor(address _owner ) { - owner=_owner; + + constructor(address _owner) { + owner = _owner; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -46,8 +45,8 @@ contract SystemConfigManager is Initializable { ) external initializer returns (address proxyRouter) { console.log(owner); console.log(msg.sender); - - require(owner==msg.sender,"OnlyOwner"); + + require(owner == msg.sender, "OnlyOwner"); require(vars.ownerAddress != address(0), "Owner address cannot be zero"); @@ -123,7 +122,6 @@ contract SystemConfigManager is Initializable { // Initialize RVaultAsset. RVaultAsset(payable(batchAddressesSet.batch3Addrs.rVaultAsset)).initialize(rVaultAssetInitializeParams); - proxyConfigurator.setRvaultAssetForUnderlying( batchAddressesSet.batch1Addrs.underlying, batchAddressesSet.batch3Addrs.rVaultAsset ); diff --git a/scripts/batchDeploymentScript/batchDeploy.s.sol b/scripts/batchDeploymentScript/batchDeploy.s.sol index 7ef2fa00..f1c53143 100644 --- a/scripts/batchDeploymentScript/batchDeploy.s.sol +++ b/scripts/batchDeploymentScript/batchDeploy.s.sol @@ -111,7 +111,7 @@ contract MainDeployer is Script { SystemConfigManager systemConfigManager; function deployBatches() internal { - string memory systemConfigManagerSalt = "systemConfigManager"; + string memory systemConfigManagerSalt = "systemConfigManager3"; string memory batchDeployer1Salt = "batchDeployer1"; string memory batchDeployer2Salt = "batchDeployer2"; string memory batchDeployer3Salt = "batchDeployer3"; @@ -119,9 +119,10 @@ contract MainDeployer is Script { systemConfigManager = SystemConfigManager( Create2Helper.deployContractWithArgs( - "SystemConfigManager", systemConfigManagerSalt, type(SystemConfigManager).creationCode, abi.encode( - vm.parseTomlAddress(deployConfig, ".owner.address") - ) + "SystemConfigManager", + systemConfigManagerSalt, + type(SystemConfigManager).creationCode, + abi.encode(vm.parseTomlAddress(deployConfig, ".owner.address")) ) ); address initialOwner = address(systemConfigManager); @@ -252,6 +253,15 @@ contract MainDeployer is Script { rTokenImpl: batchAddressesSet.batch3Addrs.rToken, eventValidator: batchAddressesSet.batch1Addrs.eventValidator }); + // uint256[] memory lzEidsChains; + uint256[] memory lzEids_ = vm.parseTomlUintArray(deployConfig, ".lz_eids_config.eids"); + uint32[] memory lzEids = new uint32[](lzEids_.length); + for (uint256 k = 0; k < lzEids_.length; k++) { + lzEids[k] = uint32(lzEids_[k]); + } + // lzEidsChains = vm.parseTomlUintArray(deployConfig, ".lz_eids_config.chains"); + // lzEids = vm.parseTomlUintArray(deployConfig, ".lz_eids_config.eids"); + RVaultAssetInitializeParams memory rvaultAssetInitializeParams = RVaultAssetInitializeParams( batchAddressesSet.batch1Addrs.superAsset, vars.lpProvider, @@ -264,7 +274,9 @@ contract MainDeployer is Script { vm.parseTomlUint(deployConfig, ".rvault_asset.max_deposit_limit"), uint128(vm.parseTomlUint(deployConfig, ".layerzero.lz_receive_gas_limit")), uint128(vm.parseTomlUint(deployConfig, ".layerzero.lz_compose_gas_limit")), - vm.parseTomlAddress(deployConfig, ".owner.address") + vm.parseTomlAddress(deployConfig, ".owner.address"), + vm.parseTomlUintArray(deployConfig, ".lz_eids_config.chains"), + lzEids ); BatchDataTypes.SuperAssetInitParams memory superAssetInitParams = BatchDataTypes.SuperAssetInitParams( @@ -293,7 +305,7 @@ contract MainDeployer is Script { ); vars.relayers = new address[](vm.parseTomlUint(deployConfig, ".relayers.total")); for (uint256 i = 1; i <= vars.relayers.length; i++) { - vars.relayers[i-1] = + vars.relayers[i - 1] = vm.parseTomlAddress(deployConfig, string.concat(".relayers.address", Strings.toString(i))); } diff --git a/scripts/deploy.s.sol b/scripts/deploy.s.sol deleted file mode 100644 index 7a611073..00000000 --- a/scripts/deploy.s.sol +++ /dev/null @@ -1,420 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity 0.8.25; - -import {ILendingPoolConfigurator} from "src/interfaces/ILendingPoolConfigurator.sol"; -import {ILendingPoolAddressesProvider} from "src/interfaces/ILendingPoolAddressesProvider.sol"; -import {Script, console} from "forge-std/Script.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {LendingPoolAddressesProvider} from "src/configuration/LendingPoolAddressesProvider.sol"; -import {LendingPoolConfigurator} from "src/LendingPoolConfigurator.sol"; -import {LendingPool} from "src/LendingPool.sol"; -import {DefaultReserveInterestRateStrategy} from "src/DefaultReserveInterestRateStrategy.sol"; -import {LendingRateOracle} from "src/LendingRateOracle.sol"; -import {SuperAsset} from "src/SuperAsset.sol"; -import {RToken} from "src/tokenization/RToken.sol"; -import {VariableDebtToken} from "src/tokenization/VariableDebtToken.sol"; -import {L2NativeSuperchainERC20} from "src/libraries/op/L2NativeSuperchainERC20.sol"; -import {Router} from "src/Router.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {MockLayerZeroEndpointV2} from "../test/utils/MockLayerZeroEndpointV2.sol"; -import {TestERC20} from "../test/utils/TestERC20.sol"; -import {MockPriceOracle} from "../test/utils/MockPriceOracle.sol"; -import {EventValidator} from "src/libraries/EventValidator.sol"; -import {LendingPoolCollateralManager} from "src/LendingPoolCollateralManager.sol"; -import {RVaultAsset} from "src/RVaultAsset.sol"; -import {RVaultAssetInitializeParams} from "src/interfaces/IRVaultAsset.sol"; - -contract LendingPoolDeployer is Script { - string deployConfig; - address treasury; - address incentivesController; - address crossL2ProverAddress; - address currentChainWethAddress; - address relayer; - address poolAdmin; - address ownerAddress; - address lzEndpoint; - address lzDelegate; - - struct DeployedContracts { - address lendingPoolImpl; - address lendingPool; - address underlying; - address superAsset; - address rVaultAsset; - address rTokenImpl; - address variableDebtTokenImpl; - address proxyAdmin; - address lendingPoolAddressesProvider; - address lendingPoolConfigurator; - address defaultReserveInterestRateStrategy; - address lendingRateOracle; - address router; - address routerImpl; - address eventValidator; - address lpCollateralManager; - } - - address underlying; - ILendingPoolAddressesProvider provider; - address delegate; - string name; - string symbol; - uint8 decimals; - uint256 withdrawCoolDownPeriod; - uint256 maxDepositLimit; - uint128 lzReceiveGasLimit; - uint128 lzComposeGasLimit; - - DeployedContracts deployedContracts; - - constructor() { - string memory deployConfigPath = vm.envOr("DEPLOY_CONFIG_PATH", string("/configs/deploy-config.toml")); - deployConfig = vm.readFile(string.concat(vm.projectRoot(), deployConfigPath)); - } - - modifier broadcast() { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - - _; - vm.stopBroadcast(); - } - - function run() public { - console.log("this", address(this)); - // vm.createSelectFork(vm.parseTomlString(deployConfig, ".forks.chain_c_rpc_url")); - - treasury = vm.parseTomlAddress(deployConfig, ".treasury.address"); - incentivesController = vm.parseTomlAddress(deployConfig, ".incentives_controller.address"); - crossL2ProverAddress = vm.parseTomlAddress(deployConfig, ".forks.chain_c_cross_l2_prover_address"); - currentChainWethAddress = vm.parseTomlAddress(deployConfig, ".forks.chain_c_weth"); - relayer = vm.parseTomlAddress(deployConfig, ".relayers.address1"); - poolAdmin = vm.parseTomlAddress(deployConfig, ".pool_admin.address"); - ownerAddress = vm.parseTomlAddress(deployConfig, ".owner.address"); - lzEndpoint = vm.parseTomlAddress(deployConfig, ".forks.chain_c_lz_endpoint_v2"); - lzDelegate = address(0); - deployFullLendingPool(); - outputDeploymentResult(); - } - - function deployFullLendingPool() public broadcast { - deployedContracts.underlying = deployUnderlying(); - deployedContracts.superAsset = deploySuperAsset(); - deployedContracts.proxyAdmin = deployProxyAdmin(); - deployedContracts.lendingPoolAddressesProvider = deployLendingPoolAddressesProvider(); - deployedContracts.lendingPoolImpl = deployContract("LendingPool", ".lending_pool.salt", ""); - deployedContracts.lendingPoolConfigurator = - deployContract("LendingPoolConfigurator", ".lending_pool_configurator.salt", ""); - deployedContracts.defaultReserveInterestRateStrategy = - deployDefaultReserveInterestRateStrategy(deployedContracts.lendingPoolAddressesProvider); - deployedContracts.lendingRateOracle = - deployContractWithArgs("MockPriceOracle", ".price_oracle.salt", abi.encode(ownerAddress)); - - ILendingPoolAddressesProvider lpAddressProvider = - ILendingPoolAddressesProvider(deployedContracts.lendingPoolAddressesProvider); - - lpAddressProvider.setLendingPoolImpl(deployedContracts.lendingPoolImpl); - LendingPool proxyLp = LendingPool(lpAddressProvider.getLendingPool()); - deployedContracts.lendingPool = address(proxyLp); - deployedContracts.lpCollateralManager = address(new LendingPoolCollateralManager()); - deployedContracts.eventValidator = deployEventValidator(); - - (deployedContracts.router, deployedContracts.routerImpl) = deployRouter(); - - deployedContracts.rVaultAsset = deployRVaultAsset(); - deployedContracts.rTokenImpl = deployContract("RToken", ".rToken.salt", ""); - vm.parseTomlString(deployConfig, ".variable_debt_token.salt"); - deployedContracts.variableDebtTokenImpl = deployContract("VariableDebtToken", ".variable_debt_token.salt", ""); - // Configure LayerZero parameters - maxDepositLimit = 1 ether * vm.parseTomlUint(deployConfig, ".rvault_asset.max_deposit_limit"); - withdrawCoolDownPeriod = vm.parseTomlUint(deployConfig, ".rvault_asset.withdraw_cool_down_period"); - lzReceiveGasLimit = uint128(vm.parseTomlUint(deployConfig, ".layerzero.lz_receive_gas_limit")); - lzComposeGasLimit = uint128(vm.parseTomlUint(deployConfig, ".layerzero.lz_compose_gas_limit")); - RVaultAsset(payable(deployedContracts.rVaultAsset)).setAllLimits( - lzReceiveGasLimit, lzComposeGasLimit, maxDepositLimit - ); - - RVaultAsset(payable(deployedContracts.rVaultAsset)).initialize( - RVaultAssetInitializeParams( - address(deployedContracts.superAsset), - ILendingPoolAddressesProvider(address(deployedContracts.lendingPoolAddressesProvider)), - lzEndpoint, - lzDelegate, - vm.parseTomlString(deployConfig, ".rvault_asset.name"), - vm.parseTomlString(deployConfig, ".rvault_asset.symbol"), - uint8(vm.parseTomlUint(deployConfig, ".underlying.decimals")), - withdrawCoolDownPeriod, - maxDepositLimit, - lzReceiveGasLimit, - lzComposeGasLimit, - vm.parseTomlAddress(deployConfig, ".owner.address") - ) - ); - - // - // Set critical protocol params - - lpAddressProvider.setPoolAdmin(ownerAddress); - lpAddressProvider.setRelayerStatus(relayer, true); - lpAddressProvider.setPriceOracle(deployedContracts.lendingRateOracle); - lpAddressProvider.setLendingPoolConfiguratorImpl(deployedContracts.lendingPoolConfigurator); - // Configure reserve parameters - LendingPoolConfigurator(deployedContracts.lendingPoolConfigurator).initialize( - lpAddressProvider, deployedContracts.proxyAdmin - ); - - lpAddressProvider.setLendingPoolCollateralManager(deployedContracts.lpCollateralManager); - - LendingPoolConfigurator lendingPoolConfigurator = - LendingPoolConfigurator(lpAddressProvider.getLendingPoolConfigurator()); - - // Initialize core protocol components - LendingPool(deployedContracts.lendingPoolImpl).initialize(lpAddressProvider); - - // Initialize price oracle values - MockPriceOracle(deployedContracts.lendingRateOracle).setPrice(deployedContracts.underlying, 1 ether); - - lendingPoolConfigurator.activateReserve(deployedContracts.rVaultAsset); - lendingPoolConfigurator.enableBorrowingOnReserve(deployedContracts.rVaultAsset); - lendingPoolConfigurator.configureReserveAsCollateral( - deployedContracts.rVaultAsset, - 8000, // LTV - 8000, // Liquidation threshold - 10500 // Liquidation bonus - ); - - // Link underlying asset to its rVault - lendingPoolConfigurator.setRvaultAssetForUnderlying(deployedContracts.underlying, deployedContracts.rVaultAsset); - configureReserves(lpAddressProvider); - - // handing over to true admins - lpAddressProvider.setPoolAdmin(poolAdmin); - // transfer ownership of lendingPoolAddressesProvider - ILendingPoolAddressesProvider(deployedContracts.lendingPoolAddressesProvider).setProxyAdmin( - deployedContracts.proxyAdmin - ); - } - - function configureReserves(ILendingPoolAddressesProvider lpAddressProvider) internal { - LendingPoolConfigurator proxyConfigurator = - LendingPoolConfigurator(lpAddressProvider.getLendingPoolConfigurator()); - // Initialize reserve with token ecosystem - - ILendingPoolConfigurator.InitReserveInput[] memory reserves = new ILendingPoolConfigurator.InitReserveInput[](1); - reserves[0] = ILendingPoolConfigurator.InitReserveInput({ - rTokenName: vm.parseTomlString(deployConfig, ".rToken.name"), - rTokenSymbol: vm.parseTomlString(deployConfig, ".rToken.symbol"), - variableDebtTokenImpl: deployedContracts.variableDebtTokenImpl, - variableDebtTokenName: vm.parseTomlString(deployConfig, ".variable_debt_token.name"), - variableDebtTokenSymbol: vm.parseTomlString(deployConfig, ".variable_debt_token.symbol"), - interestRateStrategyAddress: deployedContracts.defaultReserveInterestRateStrategy, - treasury: treasury, - incentivesController: incentivesController, - superAsset: deployedContracts.superAsset, - underlyingAsset: deployedContracts.rVaultAsset, - underlyingAssetDecimals: uint8(vm.parseTomlUint(deployConfig, ".underlying.decimals")), - underlyingAssetName: vm.parseTomlString(deployConfig, ".underlying.name"), - params: "v1", - salt: "initial", - rTokenImpl: deployedContracts.rTokenImpl, - eventValidator: deployedContracts.eventValidator - }); - - proxyConfigurator.batchInitReserve(reserves); - } - - function _implSalt(string memory salt) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(salt)); - } - - function outputDeploymentResult() internal { - console.log("Outputting deployment result"); - string memory obj = "result"; - - vm.serializeAddress(obj, "underlying", deployedContracts.underlying); - vm.serializeAddress(obj, "rTokenImpl", deployedContracts.rTokenImpl); - vm.serializeAddress(obj, "variableDebtTokenImpl", deployedContracts.variableDebtTokenImpl); - vm.serializeAddress(obj, "proxyAdmin", deployedContracts.proxyAdmin); - vm.serializeAddress(obj, "lendingPoolAddressesProvider", deployedContracts.lendingPoolAddressesProvider); - vm.serializeAddress(obj, "superAsset", deployedContracts.superAsset); - vm.serializeAddress(obj, "lendingPool", deployedContracts.lendingPool); - vm.serializeAddress(obj, "lendingPoolConfigurator", deployedContracts.lendingPoolConfigurator); - vm.serializeAddress( - obj, "defaultReserveInterestRateStrategy", deployedContracts.defaultReserveInterestRateStrategy - ); - vm.serializeAddress(obj, "lendingRateOracle", deployedContracts.lendingRateOracle); - string memory jsonOutput = vm.serializeAddress(obj, "router", deployedContracts.router); - - vm.writeJson(jsonOutput, "deployment.json"); - } - - function deployContract(string memory contractName, string memory saltKey, bytes memory constructorArgs) - internal - returns (address) - { - bytes memory creationCode = _getCreationCode(contractName); - return _deployWithCreate2(contractName, saltKey, creationCode, constructorArgs); - } - - function deployContractWithArgs(string memory contractName, string memory saltKey, bytes memory constructorArgs) - internal - returns (address) - { - bytes memory creationCode = _getCreationCode(contractName); - return _deployWithCreate2(contractName, saltKey, creationCode, constructorArgs); - } - - function _getCreationCode(string memory contractName) internal pure returns (bytes memory) { - if (keccak256(bytes(contractName)) == keccak256(bytes("RToken"))) { - return type(RToken).creationCode; - } - if (keccak256(bytes(contractName)) == keccak256(bytes("Router"))) { - return type(Router).creationCode; - } - - if (keccak256(bytes(contractName)) == keccak256(bytes("VariableDebtToken"))) { - return type(VariableDebtToken).creationCode; - } - if (keccak256(bytes(contractName)) == keccak256(bytes("LendingPool"))) { - return type(LendingPool).creationCode; - } - if (keccak256(bytes(contractName)) == keccak256(bytes("LendingPoolConfigurator"))) { - return type(LendingPoolConfigurator).creationCode; - } - if (keccak256(bytes(contractName)) == keccak256(bytes("MockPriceOracle"))) { - return type(MockPriceOracle).creationCode; - } - - revert("Unknown contract"); - } - - function _deployWithCreate2( - string memory contractName, - string memory saltKey, - bytes memory creationCode, - bytes memory constructorArgs - ) internal returns (address) { - string memory salt = vm.parseTomlString(deployConfig, saltKey); - bytes memory initCode = abi.encodePacked(creationCode, constructorArgs); - bytes32 saltBytes = _implSalt(salt); - address preComputedAddress = vm.computeCreate2Address(saltBytes, keccak256(initCode)); - - if (preComputedAddress.code.length > 0) { - console.log("%s already deployed at %s", contractName, preComputedAddress); - return preComputedAddress; - } - - address addr; - assembly { - addr := create2(0, add(initCode, 0x20), mload(initCode), saltBytes) - if iszero(extcodesize(addr)) { revert(0, 0) } - } - console.log("Deployed %s at: %s", contractName, addr); - return addr; - } - - function deployUnderlying() internal returns (address) { - return _deployWithCreate2( - "TestERC20", - ".underlying.salt", - type(TestERC20).creationCode, - abi.encode( - vm.parseTomlString(deployConfig, ".underlying.name"), - vm.parseTomlString(deployConfig, ".underlying.symbol"), - vm.parseTomlUint(deployConfig, ".underlying.decimals"), - vm.parseTomlUint(deployConfig, ".owner.address") - ) - ); - } - //deply event validator - - function deployEventValidator() internal returns (address) { - return _deployWithCreate2( - "EventValidator", - ".event_validator.salt", - type(EventValidator).creationCode, - abi.encode(crossL2ProverAddress) - ); - } - - function deploySuperAsset() internal returns (address) { - return _deployWithCreate2( - "SuperAsset", - ".super_token.salt", - type(SuperAsset).creationCode, - abi.encode( - deployedContracts.underlying, - vm.parseTomlString(deployConfig, ".super_token.name"), - vm.parseTomlString(deployConfig, ".super_token.symbol"), - currentChainWethAddress - ) - ); - } - - function deployRVaultAsset() internal returns (address) { - return _deployWithCreate2("RVaultAsset", ".rvault_asset.salt", type(RVaultAsset).creationCode, ""); - } - - function deployProxyAdmin() internal returns (address) { - return _deployWithCreate2( - "ProxyAdmin", ".proxy_admin.salt", type(ProxyAdmin).creationCode, abi.encode(ownerAddress) - ); - } - - function deployLendingPoolAddressesProvider() internal returns (address) { - bytes32 lpType = keccak256(bytes(vm.parseTomlString(deployConfig, ".lending_pool_addresses_provider.lpType"))); - - return _deployWithCreate2( - "LendingPoolAddressesProvider", - ".lending_pool_addresses_provider.salt", - type(LendingPoolAddressesProvider).creationCode, - abi.encode( - vm.parseTomlString(deployConfig, ".lending_pool_addresses_provider.marketId"), - ownerAddress, - ownerAddress, - lpType - ) - ); - } - - function deployDefaultReserveInterestRateStrategy(address lpAddressProvider) internal returns (address) { - provider = ILendingPoolAddressesProvider(lpAddressProvider); - - bytes memory constructorArgs = abi.encode( - provider, - vm.parseTomlUint(deployConfig, ".default_reserve_interest_rate_strategy.optimalUtilizationRate"), - vm.parseTomlUint(deployConfig, ".default_reserve_interest_rate_strategy.baseVariableBorrowRate"), - vm.parseTomlUint(deployConfig, ".default_reserve_interest_rate_strategy.variableRateSlope1"), - vm.parseTomlUint(deployConfig, ".default_reserve_interest_rate_strategy.variableRateSlope2") - ); - - return _deployWithCreate2( - "DefaultReserveInterestRateStrategy", - ".default_reserve_interest_rate_strategy.salt", - type(DefaultReserveInterestRateStrategy).creationCode, - constructorArgs - ); - } - - function deployRouter() internal returns (address, address) { - // Deploy implementation - address routerImpl = deployContract("Router", ".router.salt", ""); - - // Deploy proxy - string memory salt = vm.parseTomlString(deployConfig, ".router.salt"); - bytes memory initData = abi.encodeWithSelector( - Router.initialize.selector, - deployedContracts.lendingPool, - deployedContracts.lendingPoolAddressesProvider, - deployedContracts.eventValidator - ); - - TransparentUpgradeableProxy proxy = - new TransparentUpgradeableProxy{salt: _implSalt(salt)}(routerImpl, deployedContracts.proxyAdmin, initData); - - console.log("Deployed Router Proxy at: %s", address(proxy)); - return (address(proxy), routerImpl); - } -} diff --git a/simulate_deploy.sh b/simulate_deploy.sh new file mode 100644 index 00000000..77b1c94c --- /dev/null +++ b/simulate_deploy.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo "Starting deployments across multiple chains..." + +echo "Deploying to Optimism Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://opt-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --sender 0x5377679614bc0BB997d82D11D79A87e3c5695848 + +echo "Deploying to Arbitrum Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://arb-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --sender 0x5377679614bc0BB997d82D11D79A87e3c5695848 + +echo "Deploying to Base Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://base-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --sender 0x5377679614bc0BB997d82D11D79A87e3c5695848 + +echo "Deploying to Unichain Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://unichain-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --sender 0x5377679614bc0BB997d82D11D79A87e3c5695848 + +echo "All deployments completed!" \ No newline at end of file diff --git a/src/LendingPool.sol b/src/LendingPool.sol index d9629e02..972e93fd 100644 --- a/src/LendingPool.sol +++ b/src/LendingPool.sol @@ -58,6 +58,16 @@ contract LendingPool is Initializable, LendingPoolStorage, SuperPausable { event logAddresses(address[] addresses); event RVaultAssetUpdated(address asset, address rVaultAsset); + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* Debug Logs */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + event logger(string message); + event loggerBytes(bytes message); + event loggerBytes32(bytes32 message); + event loggerUint(uint256); + event loggerAddress(address); + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* Modifiers */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -628,7 +638,7 @@ contract LendingPool is Initializable, LendingPoolStorage, SuperPausable { } } - function getRVaultAssetOrRevert(address asset) public returns (address rVaultAsset) { + function getRVaultAssetOrRevert(address asset) public view returns (address rVaultAsset) { rVaultAsset = _rVaultAsset[asset]; if (rVaultAsset == address(0)) revert RVAULT_NOT_FOUND_FOR_ASSET(); } diff --git a/src/LendingPoolStorage.sol b/src/LendingPoolStorage.sol index 9a582eef..157fd150 100644 --- a/src/LendingPoolStorage.sol +++ b/src/LendingPoolStorage.sol @@ -22,7 +22,7 @@ contract LendingPoolStorage { */ mapping(address rVaultAsset => DataTypes.ReserveData) internal _reserves; - mapping(address anything => address rVaultAsset) internal _rVaultAsset; + mapping(address underlying => address rVaultAsset) internal _rVaultAsset; /** * @notice Mapping of user configuration by user address. diff --git a/src/RVaultAsset.sol b/src/RVaultAsset.sol index af5bd642..1cc3f737 100644 --- a/src/RVaultAsset.sol +++ b/src/RVaultAsset.sol @@ -7,7 +7,10 @@ import {ILendingPoolAddressesProvider} from "src/interfaces/ILendingPoolAddresse import {ISuperAsset} from "src/interfaces/ISuperAsset.sol"; import {ILendingPool} from "src/interfaces/ILendingPool.sol"; import { - Origin, MessagingReceipt, ILayerZeroEndpointV2 + Origin, + MessagingReceipt, + ILayerZeroEndpointV2, + MessagingParams } from "src/libraries/helpers/layerzero/ILayerZeroEndpointV2.sol"; import {Initializable} from "@solady/utils/Initializable.sol"; @@ -103,6 +106,11 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { lzComposeGasLimit = params.lzComposeGasLimit; _initializeSuperOwner(uint64(block.chainid), params.owner); OFT__Init(params.lzEndpoint, params.delegate, params.decimals); + + for (uint256 i = 0; i < params.lzEidChains.length; i++) { + chainToEid[params.lzEidChains[i]] = params.lzEids[i]; + _setPeer(params.lzEids[i], bytes32(uint256(uint160(address(this))))); + } } /// @param shares - the amount of shares to mint @@ -114,7 +122,9 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { /// @param assets - the amount of assets to deposit /// @param receiver - the address to which the assets are deposited function deposit(uint256 assets, address receiver) public returns (uint256) { - if (totalAssets() + assets > maxDepositLimit) revert DEPOSIT_LIMIT_EXCEEDED(); + if (totalAssets() + assets > maxDepositLimit) { + revert DEPOSIT_LIMIT_EXCEEDED(); + } balances[receiver] += assets; super._mint(receiver, assets); @@ -132,11 +142,21 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { _bridge(receiverOfUnderlying, toChainId, amount); } else { _burn(msg.sender, amount); - if (pool_type == 1) { - ISuperAsset(underlying).withdraw(receiverOfUnderlying, amount); - } else { - IERC20(underlying).safeTransfer(receiverOfUnderlying, amount); - } + _handleCurrentChainBurn(receiverOfUnderlying, amount); + } + } + + function _handleCurrentChainBurn(address receiverOfUnderlying, uint256 amount) internal { + uint256 totalUnderylying = totalAssets(); + uint256 sendAmount; + // when rVaultAsset does not have enough uderlying due to bridging + if (totalUnderylying >= amount) sendAmount = amount; + else sendAmount = totalUnderylying; + + if (pool_type == 1) { + ISuperAsset(underlying).withdraw(receiverOfUnderlying, sendAmount); + } else { + IERC20(underlying).safeTransfer(receiverOfUnderlying, sendAmount); } } @@ -167,7 +187,9 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { address _underlying, uint256 _underlyingAmount ) external onlySuperAdmin { - if (!isSupportedBungeeTarget[_bungeeTarget]) revert BUNGEE_TARGET_NOT_SUPPORTED(); + if (!isSupportedBungeeTarget[_bungeeTarget]) { + revert BUNGEE_TARGET_NOT_SUPPORTED(); + } if (pool_type == 1) { ISuperAsset(_underlying).withdraw(address(this), _underlyingAmount); } @@ -198,7 +220,9 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt. msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress); - if (msgReceipt.guid == 0 && msgReceipt.nonce == 0) revert OFT_SEND_FAILED(); + if (msgReceipt.guid == 0 && msgReceipt.nonce == 0) { + revert OFT_SEND_FAILED(); + } // @dev Formulate the OFT receipt. oftReceipt = OFTReceipt(0, 0); @@ -214,7 +238,9 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { bytes calldata /*_extraData*/ ) public payable override { (address receiverOfUnderlying, uint256 amount, address oftTxCaller) = OFTLogic.decodeMessage(_message); - if (msg.sender != address(endpoint) && oftTxCaller != address(this)) revert UNAUTHORIZED_SENDER(); + if (msg.sender != address(endpoint) && oftTxCaller != address(this)) { + revert UNAUTHORIZED_SENDER(); + } if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) { revert OnlyPeer(_origin.srcEid, _origin.sender); } @@ -244,27 +270,36 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { (SendParam memory sendParam, MessagingFee memory fee) = getFeeQuote(receiverOfUnderlying, toChainId, amount); _send(sendParam, fee, payable(address(this))); } else { - if (pool_type == 1) { - ISuperAsset(underlying).withdraw(receiverOfUnderlying, amount); - } else { - IERC20(underlying).safeTransfer(receiverOfUnderlying, amount); - } + _handleCurrentChainBurn(receiverOfUnderlying, amount); } } + event loggerUint(uint256); + event logger(string); + event loggerBytes(bytes); + event loggerBytes32(bytes32); + function getFeeQuote(address receiverOfUnderlying, uint256 toChainId, uint256 amount) public view - returns (SendParam memory sendParam, MessagingFee memory) + returns (SendParam memory sendParam, MessagingFee memory fee) { bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(lzReceiveGasLimit, 0) .addExecutorLzComposeOption(0, lzComposeGasLimit, 0); - bytes memory compose_message = OFTLogic.encodeMessage(receiverOfUnderlying, amount); sendParam = SendParam( chainToEid[toChainId], bytes32(uint256(uint160(address(this)))), 0, 0, options, compose_message, "" ); - return (sendParam, quoteSend(sendParam, false)); + + // same as in send + uint256 amountReceivedLD = amount; + // @dev Builds the options and OFT message to quote in the endpoint. + bytes memory message; + (message, options) = _buildMsgAndOptions(sendParam, amountReceivedLD); + fee = endpoint.quote( + MessagingParams(sendParam.dstEid, _getPeerOrRevert(sendParam.dstEid), message, options, false), + address(this) + ); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -276,7 +311,7 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { } // Setter function for chain to EID mapping - function setChainToEid(uint256 _chainId, uint32 _eid) public onlySuperAdmin { + function setChainToEid(uint256 _chainId, uint32 _eid) external onlySuperAdmin { chainToEid[_chainId] = _eid; } diff --git a/src/Router.sol b/src/Router.sol index aa0868f3..4b5e5f61 100644 --- a/src/Router.sol +++ b/src/Router.sol @@ -71,7 +71,7 @@ contract Router is Initializable, SuperPausable { bytes calldata _proof, uint256[] calldata _logIndex ) external whenNotPaused { - if (ILendingPoolAddressesProvider(addressesProvider).getRelayerStatus(msg.sender)!=true) revert NOT_RELAYER(); + if (ILendingPoolAddressesProvider(addressesProvider).getRelayerStatus(msg.sender) != true) revert NOT_RELAYER(); if (_mode == ValidationMode.CROSS_L2_PROVER_RECEIPT) { EventValidator(eventValidator).validate(_mode, _identifier[0], _data, _logIndex, _proof); diff --git a/src/configuration/LendingPoolAddressesProvider.sol b/src/configuration/LendingPoolAddressesProvider.sol index 47958a81..f669b009 100644 --- a/src/configuration/LendingPoolAddressesProvider.sol +++ b/src/configuration/LendingPoolAddressesProvider.sol @@ -212,12 +212,11 @@ contract LendingPoolAddressesProvider is SuperOwnable { _addresses[LENDING_RATE_ORACLE] = lendingRateOracle; emit LendingRateOracleUpdated(lendingRateOracle); } - - function getRelayerStatus(address adr) external view returns (bool){ + + function getRelayerStatus(address adr) external view returns (bool) { return isRelayer[adr]; } - function setRelayerStatus(address relayer, bool isActive) external onlyOwner { isRelayer[relayer] = isActive; emit RelayerStatusUpdated(relayer, isActive); diff --git a/src/interfaces/IRVaultAsset.sol b/src/interfaces/IRVaultAsset.sol index fef2f84d..908d9fce 100644 --- a/src/interfaces/IRVaultAsset.sol +++ b/src/interfaces/IRVaultAsset.sol @@ -20,6 +20,8 @@ struct RVaultAssetInitializeParams { uint128 lzReceiveGasLimit; uint128 lzComposeGasLimit; address owner; + uint256[] lzEidChains; + uint32[] lzEids; } interface IRVaultAsset { diff --git a/src/libraries/helpers/layerzero/OAppOptionsType3.sol b/src/libraries/helpers/layerzero/OAppOptionsType3.sol index bb331d60..f3bd2b3d 100644 --- a/src/libraries/helpers/layerzero/OAppOptionsType3.sol +++ b/src/libraries/helpers/layerzero/OAppOptionsType3.sol @@ -78,23 +78,30 @@ abstract contract OAppOptionsType3 is IOAppOptionsType3, Ownable { if (_extraOptions.length >= 2) { _assertOptionsType3(_extraOptions); // @dev Remove the first 2 bytes containing the type from the _extraOptions and combine with enforced. - // TODO: @tabshaikh check this math - // return bytes.concat(enforced, _extraOptions[2:]); - assembly { - mstore(add(_extraOptions, 32), sub(mload(_extraOptions), 2)) // reduce length by 2 - mstore(_extraOptions, add(mload(add(_extraOptions, 32)), 2)) // increase data pointer by 2 + + bytes memory result = new bytes(enforced.length + _extraOptions.length - 2); + + // Copy enforcedOptions into result + for (uint256 i = 0; i < enforced.length; i++) { + result[i] = enforced[i]; + } + + // Copy _extraOptions[2:] into result + for (uint256 i = 2; i < _extraOptions.length; i++) { + result[enforced.length + i - 2] = _extraOptions[i]; } - return bytes.concat(enforced, _extraOptions); + + return result; } // No valid set of options was found. revert InvalidOptions(_extraOptions); } - /** * @dev Internal function to assert that options are of type 3. * @param _options The options to be checked. */ + function _assertOptionsType3(bytes memory _options) internal pure virtual { uint16 optionsType; assembly { diff --git a/src/tokenization/RToken.sol b/src/tokenization/RToken.sol index eeb9b9e9..3c2fce65 100644 --- a/src/tokenization/RToken.sol +++ b/src/tokenization/RToken.sol @@ -28,6 +28,7 @@ import {Predeploys} from "../libraries/Predeploys.sol"; import {MessagingFee} from "src/libraries/helpers/layerzero/ILayerZeroEndpointV2.sol"; import {DataTypes} from "src/libraries/types/DataTypes.sol"; import {LendingPool} from "src/LendingPool.sol"; +import {SendParam} from "src/libraries/helpers/layerzero/IOFT.sol"; /** * @title Aave ERC20 RToken @@ -61,9 +62,15 @@ contract RToken is Initializable, IncentivizedERC20, IRToken, SuperPausable { uint256 public totalCrosschainUnderlyingAssets; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ - /* Events */ + /* Debug Logs */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + event logger(string message); + event loggerBytes(bytes message); + event loggerBytes32(bytes32 message); + event loggerUint(uint256); + event loggerAddress(address); + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* Modifiers */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -88,7 +95,7 @@ contract RToken is Initializable, IncentivizedERC20, IRToken, SuperPausable { } function _onlyRelayer() internal view { - require(_addressesProvider.getRelayerStatus(msg.sender)==true, ONLY_RELAYER_CALL); + require(_addressesProvider.getRelayerStatus(msg.sender) == true, ONLY_RELAYER_CALL); } modifier onlyLendingPoolConfigurator() { @@ -225,13 +232,18 @@ contract RToken is Initializable, IncentivizedERC20, IRToken, SuperPausable { MessagingFee memory fee; address rVaultAddress = _underlyingAsset; - // todo: if lp rtoken does not have enough underlying to burn, skip burning - if (IERC20(rVaultAddress).balanceOf(address(this)) >= amount) { - if (toChainId != block.chainid) { - (, fee) = IRVaultAsset(rVaultAddress).getFeeQuote(receiverOfUnderlying, toChainId, amount); - } - IRVaultAsset(rVaultAddress).burn{value: fee.nativeFee}(receiverOfUnderlying, toChainId, amount); + // // todo: if lp rtoken does not have enough underlying to burn, skip burning + if (toChainId != block.chainid) { + bytes memory data = + abi.encodeWithSignature("getFeeQuote(address,uint256,uint256)", receiverOfUnderlying, toChainId, amount); + bool success; + (success, data) = (address(rVaultAddress)).call(data); + SendParam memory sendParam; + (sendParam, fee) = abi.decode(data, (SendParam, MessagingFee)); } + IRVaultAsset(rVaultAddress).burn{value: fee.nativeFee}(receiverOfUnderlying, toChainId, amount); + + totalCrosschainUnderlyingAssets -= amount; emit Transfer(user, address(0), amount); emit Burn(user, receiverOfUnderlying, amount, index); @@ -260,6 +272,7 @@ contract RToken is Initializable, IncentivizedERC20, IRToken, SuperPausable { require(amountScaled != 0, CT_INVALID_MINT_AMOUNT); _mint(user, amountScaled); crosschainUnderlyingAsset[user] += amount; + totalCrosschainUnderlyingAssets += amount; emit Transfer(address(0), user, amount); emit Mint(user, amount, index); diff --git a/test/Base.t.sol b/test/Base.t.sol index 7ffb41c7..240c24f8 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -279,6 +279,9 @@ contract Base is TestHelperOz5 { // Deploy RVaultAsset console.log("Deploying RVaultAsset"); + uint32[] memory lzEids; + uint256[] memory lzEidChains; + vm.startPrank(owner); rVaultAsset1 = address(new RVaultAsset{salt: "rVaultAsset1Impl"}(owner)); IRVaultAsset(rVaultAsset1).initialize( @@ -294,7 +297,9 @@ contract Base is TestHelperOz5 { 1 ether * vm.parseTomlUint(deployConfig, ".rvault_asset.max_deposit_limit"), uint128(vm.parseTomlUint(deployConfig, ".layerzero.lz_receive_gas_limit")), uint128(vm.parseTomlUint(deployConfig, ".layerzero.lz_compose_gas_limit")), - vm.parseTomlAddress(deployConfig, ".owner.address") + vm.parseTomlAddress(deployConfig, ".owner.address"), + lzEidChains, + lzEids ) ); @@ -312,7 +317,9 @@ contract Base is TestHelperOz5 { 1000 ether, 200000, 500000, - owner + owner, + lzEidChains, + lzEids ) ); vm.stopPrank(); diff --git a/test/LendingPool/LendingPoolTestDeposit.t.sol b/test/LendingPool/LendingPoolTestDeposit.t.sol index b4e42172..afbf4077 100644 --- a/test/LendingPool/LendingPoolTestDeposit.t.sol +++ b/test/LendingPool/LendingPoolTestDeposit.t.sol @@ -120,10 +120,14 @@ contract LendingPoolTestDeposit is LendingPoolTestBase { router.dispatch(ValidationMode.CUSTOM, _identifier, depositLocalVars._eventData, bytes(""), _logindex); /// assert cross chain balances of rtoken - assert( - RToken(payable(proxyLp.getReserveData(address(rVaultAsset1)).rTokenAddress)).totalCrosschainUnderlyingAssets( - ) == depositLocalVars.depositParams.amount - ); + uint256 totalCrcosschainUnderlying = RToken( + payable(proxyLp.getReserveData(address(rVaultAsset1)).rTokenAddress) + ).totalCrosschainUnderlyingAssets(); + console.log(totalCrcosschainUnderlying, depositLocalVars.depositParams.amount); + uint256 expectedCrosschainBalance = 2 * depositLocalVars.depositParams.amount; + // depositLocalVars.depositParams.amount on source and depositLocalVars.depositParams.amount on other chain + // so exptectedCrosschainBalance = 2*depositLocalVars.depositParams.amount + assert(totalCrcosschainUnderlying == expectedCrosschainBalance); } function getActionXConfig() diff --git a/test/LendingPool/LendingPoolTestWithdraw.t.sol b/test/LendingPool/LendingPoolTestWithdraw.t.sol index aaa600fb..cfe47c99 100644 --- a/test/LendingPool/LendingPoolTestWithdraw.t.sol +++ b/test/LendingPool/LendingPoolTestWithdraw.t.sol @@ -188,6 +188,9 @@ contract LendingPoolTestWithdraw is LendingPoolTestBase { vm.stopPrank(); // ======== Deploy RVaultAssets ======== + uint32[] memory lzEids; + uint256[] memory lzEidChains; + vm.startPrank(owner); // Deploy and initialize vault asset for chain A @@ -205,7 +208,9 @@ contract LendingPoolTestWithdraw is LendingPoolTestBase { 1000 ether, 200000, 500000, - owner + owner, + lzEidChains, + lzEids ) ); @@ -224,7 +229,9 @@ contract LendingPoolTestWithdraw is LendingPoolTestBase { 1000 ether, 200000, 500000, - owner + owner, + lzEidChains, + lzEids ) ); @@ -499,10 +506,14 @@ contract LendingPoolTestWithdraw is LendingPoolTestBase { router1.dispatch(ValidationMode.CUSTOM, _identifier, _eventData, bytes(""), _logindex); /// assert cross chain balances of rtoken - assert( - RToken(payable(proxyLp1.getReserveData(address(aRVaultAsset)).rTokenAddress)) - .totalCrosschainUnderlyingAssets() == depositEventParams.amount - ); + uint256 totalCrosschainUnderlyingAssets = RToken( + payable(proxyLp1.getReserveData(address(aRVaultAsset)).rTokenAddress) + ).totalCrosschainUnderlyingAssets(); + // depositParams.amount on source and depositParams.amount on other chain + // so exptectedCrosschainBalance = 2*depositParams.amount + uint256 expectedCrosschainBalance = 2 * depositEventParams.amount; + + assert(totalCrosschainUnderlyingAssets == expectedCrosschainBalance); // ======== Execute Withdrawal ======== // Record events and initiate withdrawal @@ -565,10 +576,10 @@ contract LendingPoolTestWithdraw is LendingPoolTestBase { router1.dispatch(ValidationMode.CUSTOM, _identifier, _eventData, bytes(""), _logindex); /// assert cross chain balances of rtoken - assert( - RToken(payable(proxyLp1.getReserveData(address(aRVaultAsset)).rTokenAddress)) - .totalCrosschainUnderlyingAssets() == 0 - ); + totalCrosschainUnderlyingAssets = RToken(payable(proxyLp1.getReserveData(address(aRVaultAsset)).rTokenAddress)) + .totalCrosschainUnderlyingAssets(); + console.log("totalCrosschainUnderlyingAssets", totalCrosschainUnderlyingAssets); + assert(totalCrosschainUnderlyingAssets == 0); // ======== Verify Cross-Chain Messages ======== verifyPackets(bEid, addressToBytes32(address(bRVaultAsset))); diff --git a/test/utils/TestERC20.sol b/test/utils/TestERC20.sol index d3e08479..aa0b79c5 100644 --- a/test/utils/TestERC20.sol +++ b/test/utils/TestERC20.sol @@ -1,30 +1,50 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {ERC20} from "@openzeppelin/contracts-v5/token/ERC20/ERC20.sol"; -import {MockERC20} from "forge-std/mocks/MockERC20.sol"; +import {ERC20} from "@solady/tokens/ERC20.sol"; import {Initializable} from "@solady/utils/Initializable.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -contract TestERC20 is Initializable, MockERC20, Ownable { +contract TestERC20 is Initializable, ERC20, Ownable { + string _name; + string _symbol; + uint8 _decimals; + constructor(address ownerAddr) Ownable(ownerAddr) { _transferOwnership(ownerAddr); } - function initialize(string memory _name, string memory _symbol, uint8 _decimals, address _owner) + function initialize(string memory name_, string memory symbol_, uint8 decimals_, address owner_) external initializer onlyOwner { - initialize(_name, _symbol, _decimals); - _transferOwnership(_owner); + _name = name_; + _symbol = symbol_; + _decimals = decimals_; + _transferOwnership(owner_); } - function mint(address to, uint256 amount) public onlyOwner { - _mint(to, amount); + function mint(address to_, uint256 amount_) public onlyOwner { + _mint(to_, amount_); } receive() external payable {} fallback() external payable {} + + /// @dev Returns the name of the token. + function name() public view override returns (string memory) { + return _name; + } + + /// @dev Returns the symbol of the token. + function symbol() public view override returns (string memory) { + return _symbol; + } + + /// @dev Returns the decimals places of the token. + function decimals() public view override returns (uint8) { + return _decimals; + } } From c2c6ae9516da479c8d7107d4ced8c37d63caacb3 Mon Sep 17 00:00:00 2001 From: 0xumarkhatab Date: Tue, 1 Apr 2025 11:13:44 +0500 Subject: [PATCH 2/5] added updated scripts --- deployment_links.md | 17 +++++++++ deployment_utils/deploy_catapulta.sh | 17 +++++++++ deployment_utils/deploy_forge.sh | 17 +++++++++ deployment_utils/deployment-explorer-links.py | 35 +++++++++++++++++++ deployment_utils/simulate_deploy_catapulta.sh | 17 +++++++++ deployment_utils/simulate_deploy_forge.sh | 17 +++++++++ faqs.md | 3 ++ 7 files changed, 123 insertions(+) create mode 100644 deployment_links.md create mode 100644 deployment_utils/deploy_catapulta.sh create mode 100644 deployment_utils/deploy_forge.sh create mode 100644 deployment_utils/deployment-explorer-links.py create mode 100644 deployment_utils/simulate_deploy_catapulta.sh create mode 100644 deployment_utils/simulate_deploy_forge.sh create mode 100644 faqs.md diff --git a/deployment_links.md b/deployment_links.md new file mode 100644 index 00000000..fbd12272 --- /dev/null +++ b/deployment_links.md @@ -0,0 +1,17 @@ +| Contract Name | Address | opSepolia | arbSepolia | baseSepolia | uniSepolia | +| --- | --- | --- | --- | --- | --- | +| DefaultReserveInterestRateStrategy | 0x19Aa597CC1DfAF301aD87278edC82E7533A33997 | [Link](https://optimism-sepolia.blockscout.com/address/0x19Aa597CC1DfAF301aD87278edC82E7533A33997) | [Link](https://sepolia.arbiscan.io/address/0x19Aa597CC1DfAF301aD87278edC82E7533A33997) | [Link](https://sepolia.basescan.org/address/0x19Aa597CC1DfAF301aD87278edC82E7533A33997) | [Link](https://unichain-sepolia.blockscout.com/address/0x19Aa597CC1DfAF301aD87278edC82E7533A33997) | +| EventValidator | 0xE00C8E19AEbd3Ba070d2E07c632630285648c1ff | [Link](https://optimism-sepolia.blockscout.com/address/0xE00C8E19AEbd3Ba070d2E07c632630285648c1ff) | [Link](https://sepolia.arbiscan.io/address/0xE00C8E19AEbd3Ba070d2E07c632630285648c1ff) | [Link](https://sepolia.basescan.org/address/0xE00C8E19AEbd3Ba070d2E07c632630285648c1ff) | [Link](https://unichain-sepolia.blockscout.com/address/0xE00C8E19AEbd3Ba070d2E07c632630285648c1ff) | +| LendingPool | 0xfF6e47F8665563327dBD2BB7a82B1A0b80F1A917 | [Link](https://optimism-sepolia.blockscout.com/address/0xfF6e47F8665563327dBD2BB7a82B1A0b80F1A917) | [Link](https://sepolia.arbiscan.io/address/0xfF6e47F8665563327dBD2BB7a82B1A0b80F1A917) | [Link](https://sepolia.basescan.org/address/0xfF6e47F8665563327dBD2BB7a82B1A0b80F1A917) | [Link](https://unichain-sepolia.blockscout.com/address/0xfF6e47F8665563327dBD2BB7a82B1A0b80F1A917) | +| LendingPoolAddressesProvider | 0xa4B4c623f6A2f6089b9Bef130abab18B713bc74e | [Link](https://optimism-sepolia.blockscout.com/address/0xa4B4c623f6A2f6089b9Bef130abab18B713bc74e) | [Link](https://sepolia.arbiscan.io/address/0xa4B4c623f6A2f6089b9Bef130abab18B713bc74e) | [Link](https://sepolia.basescan.org/address/0xa4B4c623f6A2f6089b9Bef130abab18B713bc74e) | [Link](https://unichain-sepolia.blockscout.com/address/0xa4B4c623f6A2f6089b9Bef130abab18B713bc74e) | +| LendingPoolCollateralManager | 0xCD8F85355c13F7D6b73d8dafc1E3aA4569b46a3F | [Link](https://optimism-sepolia.blockscout.com/address/0xCD8F85355c13F7D6b73d8dafc1E3aA4569b46a3F) | [Link](https://sepolia.arbiscan.io/address/0xCD8F85355c13F7D6b73d8dafc1E3aA4569b46a3F) | [Link](https://sepolia.basescan.org/address/0xCD8F85355c13F7D6b73d8dafc1E3aA4569b46a3F) | [Link](https://unichain-sepolia.blockscout.com/address/0xCD8F85355c13F7D6b73d8dafc1E3aA4569b46a3F) | +| LendingPoolConfigurator | 0x33149B447af1ACF99dbF9500AbBCAeee9Cb91833 | [Link](https://optimism-sepolia.blockscout.com/address/0x33149B447af1ACF99dbF9500AbBCAeee9Cb91833) | [Link](https://sepolia.arbiscan.io/address/0x33149B447af1ACF99dbF9500AbBCAeee9Cb91833) | [Link](https://sepolia.basescan.org/address/0x33149B447af1ACF99dbF9500AbBCAeee9Cb91833) | [Link](https://unichain-sepolia.blockscout.com/address/0x33149B447af1ACF99dbF9500AbBCAeee9Cb91833) | +| MockPriceOracle | 0xCF431ea2134bA5f51E521CA56d641cB322224504 | [Link](https://optimism-sepolia.blockscout.com/address/0xCF431ea2134bA5f51E521CA56d641cB322224504) | [Link](https://sepolia.arbiscan.io/address/0xCF431ea2134bA5f51E521CA56d641cB322224504) | [Link](https://sepolia.basescan.org/address/0xCF431ea2134bA5f51E521CA56d641cB322224504) | [Link](https://unichain-sepolia.blockscout.com/address/0xCF431ea2134bA5f51E521CA56d641cB322224504) | +| Owner | 0xcbc771976e3623ad236A4D8556897C47925B2Aaa | [Link](https://optimism-sepolia.blockscout.com/address/0xcbc771976e3623ad236A4D8556897C47925B2Aaa) | [Link](https://sepolia.arbiscan.io/address/0xcbc771976e3623ad236A4D8556897C47925B2Aaa) | [Link](https://sepolia.basescan.org/address/0xcbc771976e3623ad236A4D8556897C47925B2Aaa) | [Link](https://unichain-sepolia.blockscout.com/address/0xcbc771976e3623ad236A4D8556897C47925B2Aaa) | +| ProxyAdmin | 0xc841a4B48760c31A1f30f7D513b5971867aF2A04 | [Link](https://optimism-sepolia.blockscout.com/address/0xc841a4B48760c31A1f30f7D513b5971867aF2A04) | [Link](https://sepolia.arbiscan.io/address/0xc841a4B48760c31A1f30f7D513b5971867aF2A04) | [Link](https://sepolia.basescan.org/address/0xc841a4B48760c31A1f30f7D513b5971867aF2A04) | [Link](https://unichain-sepolia.blockscout.com/address/0xc841a4B48760c31A1f30f7D513b5971867aF2A04) | +| RToken | 0xf3091B015e049148966fd8dE345abd4411de77D9 | [Link](https://optimism-sepolia.blockscout.com/address/0xf3091B015e049148966fd8dE345abd4411de77D9) | [Link](https://sepolia.arbiscan.io/address/0xf3091B015e049148966fd8dE345abd4411de77D9) | [Link](https://sepolia.basescan.org/address/0xf3091B015e049148966fd8dE345abd4411de77D9) | [Link](https://unichain-sepolia.blockscout.com/address/0xf3091B015e049148966fd8dE345abd4411de77D9) | +| RVaultAsset | 0x0f476F635C4992F8b80b553bb50ec8b63FD142A8 | [Link](https://optimism-sepolia.blockscout.com/address/0x0f476F635C4992F8b80b553bb50ec8b63FD142A8) | [Link](https://sepolia.arbiscan.io/address/0x0f476F635C4992F8b80b553bb50ec8b63FD142A8) | [Link](https://sepolia.basescan.org/address/0x0f476F635C4992F8b80b553bb50ec8b63FD142A8) | [Link](https://unichain-sepolia.blockscout.com/address/0x0f476F635C4992F8b80b553bb50ec8b63FD142A8) | +| Router | 0xb8f62483b900d4d2937F16B6513b00980a3f707a | [Link](https://optimism-sepolia.blockscout.com/address/0xb8f62483b900d4d2937F16B6513b00980a3f707a) | [Link](https://sepolia.arbiscan.io/address/0xb8f62483b900d4d2937F16B6513b00980a3f707a) | [Link](https://sepolia.basescan.org/address/0xb8f62483b900d4d2937F16B6513b00980a3f707a) | [Link](https://unichain-sepolia.blockscout.com/address/0xb8f62483b900d4d2937F16B6513b00980a3f707a) | +| SuperAsset | 0xFD089Bd9d912eFCDe84439d40D42d42e86ff6f5a | [Link](https://optimism-sepolia.blockscout.com/address/0xFD089Bd9d912eFCDe84439d40D42d42e86ff6f5a) | [Link](https://sepolia.arbiscan.io/address/0xFD089Bd9d912eFCDe84439d40D42d42e86ff6f5a) | [Link](https://sepolia.basescan.org/address/0xFD089Bd9d912eFCDe84439d40D42d42e86ff6f5a) | [Link](https://unichain-sepolia.blockscout.com/address/0xFD089Bd9d912eFCDe84439d40D42d42e86ff6f5a) | +| Underlying | 0x69Ebd5e68364D8DE34547FFe76c2Dc527F699652 | [Link](https://optimism-sepolia.blockscout.com/address/0x69Ebd5e68364D8DE34547FFe76c2Dc527F699652) | [Link](https://sepolia.arbiscan.io/address/0x69Ebd5e68364D8DE34547FFe76c2Dc527F699652) | [Link](https://sepolia.basescan.org/address/0x69Ebd5e68364D8DE34547FFe76c2Dc527F699652) | [Link](https://unichain-sepolia.blockscout.com/address/0x69Ebd5e68364D8DE34547FFe76c2Dc527F699652) | +| VariableDebtToken | 0x6c2C1de7c0f34c7412c08b5F256DC394eA815a04 | [Link](https://optimism-sepolia.blockscout.com/address/0x6c2C1de7c0f34c7412c08b5F256DC394eA815a04) | [Link](https://sepolia.arbiscan.io/address/0x6c2C1de7c0f34c7412c08b5F256DC394eA815a04) | [Link](https://sepolia.basescan.org/address/0x6c2C1de7c0f34c7412c08b5F256DC394eA815a04) | [Link](https://unichain-sepolia.blockscout.com/address/0x6c2C1de7c0f34c7412c08b5F256DC394eA815a04) | diff --git a/deployment_utils/deploy_catapulta.sh b/deployment_utils/deploy_catapulta.sh new file mode 100644 index 00000000..8980a165 --- /dev/null +++ b/deployment_utils/deploy_catapulta.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo "Starting deployments across multiple chains..." + +echo "Deploying to Optimism Sepolia..." +catapulta script scripts/batchDeploymentScript/batchDeploy.s.sol --network optimismSepolia + +echo "Deploying to Arbitrum Sepolia..." +catapulta script scripts/batchDeploymentScript/batchDeploy.s.sol --network arbitrumSepolia + +echo "Deploying to Base Sepolia..." +catapulta script scripts/batchDeploymentScript/batchDeploy.s.sol --network baseSepolia + +echo "Deploying to Unichain Sepolia..." +catapulta script scripts/batchDeploymentScript/batchDeploy.s.sol --network unichainTestnet + +echo "All deployments completed!" diff --git a/deployment_utils/deploy_forge.sh b/deployment_utils/deploy_forge.sh new file mode 100644 index 00000000..59f4f5a5 --- /dev/null +++ b/deployment_utils/deploy_forge.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo "Starting deployments across multiple chains..." + +echo "Deploying to Optimism Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://opt-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --broadcast --sender 0xcbc771976e3623ad236A4D8556897C47925B2Aaa + +echo "Deploying to Arbitrum Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://arb-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --broadcast --sender 0xcbc771976e3623ad236A4D8556897C47925B2Aaa + +echo "Deploying to Base Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://base-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --broadcast --sender 0xcbc771976e3623ad236A4D8556897C47925B2Aaa + +echo "Deploying to Unichain Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://unichain-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --broadcast --sender 0xcbc771976e3623ad236A4D8556897C47925B2Aaa + +echo "All deployments completed!" \ No newline at end of file diff --git a/deployment_utils/deployment-explorer-links.py b/deployment_utils/deployment-explorer-links.py new file mode 100644 index 00000000..968ae263 --- /dev/null +++ b/deployment_utils/deployment-explorer-links.py @@ -0,0 +1,35 @@ +import json + +# Define the base explorer URLs for each chain +explorers = { + "opSepolia": "https://optimism-sepolia.blockscout.com/address/", + "arbSepolia": "https://sepolia.arbiscan.io/address/", + "baseSepolia": "https://sepolia.basescan.org/address/", + "uniSepolia": "https://unichain-sepolia.blockscout.com/address/" +} + +# Load the deployment.json file +with open("deployment.json", "r") as f: + deployment = json.load(f) + +# Create Markdown table header +header = "| Contract Name | Address | opSepolia | arbSepolia | baseSepolia | uniSepolia |\n" +separator = "| --- | --- | --- | --- | --- | --- |\n" + +table = header + separator + +# Construct a table row for each contract +for contract_name, address in deployment.items(): + row = f"| {contract_name} | {address} | " + links = [] + for chain, base_url in explorers.items(): + link = f"[Link]({base_url}{address})" + links.append(link) + row += " | ".join(links) + " |\n" + table += row + +# Save the Markdown table to deployment_links.md +with open("deployment_links.md", "w") as md_file: + md_file.write(table) + +print("Markdown table saved to deployment_links.md") diff --git a/deployment_utils/simulate_deploy_catapulta.sh b/deployment_utils/simulate_deploy_catapulta.sh new file mode 100644 index 00000000..15bf22c7 --- /dev/null +++ b/deployment_utils/simulate_deploy_catapulta.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo "Starting deployments across multiple chains..." + +echo "Deploying to Optimism Sepolia..." +catapulta script scripts/batchDeploymentScript/batchDeploy.s.sol --network optimismSepolia --simulate + +echo "Deploying to Arbitrum Sepolia..." +catapulta script scripts/batchDeploymentScript/batchDeploy.s.sol --network arbitrumSepolia --simulate + +echo "Deploying to Base Sepolia..." +catapulta script scripts/batchDeploymentScript/batchDeploy.s.sol --network baseSepolia --simulate + +echo "Deploying to Unichain Sepolia..." +catapulta script scripts/batchDeploymentScript/batchDeploy.s.sol --network unichainTestnet --simulate + +echo "All deployments completed!" diff --git a/deployment_utils/simulate_deploy_forge.sh b/deployment_utils/simulate_deploy_forge.sh new file mode 100644 index 00000000..f5ad3ffa --- /dev/null +++ b/deployment_utils/simulate_deploy_forge.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo "Starting deployments across multiple chains..." + +echo "Deploying to Optimism Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://opt-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --sender 0xcbc771976e3623ad236A4D8556897C47925B2Aaa + +echo "Deploying to Arbitrum Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://arb-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --sender 0xcbc771976e3623ad236A4D8556897C47925B2Aaa + +echo "Deploying to Base Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://base-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --sender 0xcbc771976e3623ad236A4D8556897C47925B2Aaa + +echo "Deploying to Unichain Sepolia..." +forge script scripts/batchDeploymentScript/batchDeploy.s.sol --rpc-url https://unichain-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3 --sender 0xcbc771976e3623ad236A4D8556897C47925B2Aaa + +echo "All deployments completed!" \ No newline at end of file diff --git a/faqs.md b/faqs.md new file mode 100644 index 00000000..a4ca428c --- /dev/null +++ b/faqs.md @@ -0,0 +1,3 @@ + +if txs are failing in withdraw , are you sure you funded rToken and rVaultAsset contracts? +`relayer/fund_RVault_rToken_lzGas.js` From 235bd2b497a3e08cf3819c89ef67dd16a29dd95e Mon Sep 17 00:00:00 2001 From: 0xumarkhatab Date: Tue, 1 Apr 2025 11:24:46 +0500 Subject: [PATCH 3/5] added polymer proofs --- .gitignore | 3 - configs/deploy-config.toml | 22 +- deployment.json | 14 + .../packages/layerzero-v2/ton/.gitignore | 3 + .../packages/layerzero-v2/ton/jest.config.js | 8 + .../packages/layerzero-v2/ton/package.json | 27 + .../src/apps/allStorages/class_autoload.fc | 49 + .../ton/src/apps/allStorages/main.fc | 116 ++ .../ton/src/apps/allStorages/tests/main.fc | 13 + .../ton/src/apps/baseOApp/handler.fc | 542 +++++ .../ton/src/apps/baseOApp/interface.fc | 38 + .../ton/src/apps/baseOApp/oAppMain.fc | 52 + .../ton/src/apps/baseOApp/storage.fc | 46 + .../ton/src/apps/counter/autoload.fc | 3 + .../ton/src/apps/counter/handler.fc | 219 ++ .../ton/src/apps/counter/interface.fc | 12 + .../layerzero-v2/ton/src/apps/counter/main.fc | 37 + .../ton/src/apps/counter/storage.fc | 33 + .../ton/src/apps/counter/tests/main.fc | 609 ++++++ .../ton/src/apps/counter/tests/permissions.fc | 171 ++ .../ton/src/apps/counter/tests/setters.fc | 258 +++ .../layerzero-v2/ton/src/classes/lz/Config.fc | 36 + .../ton/src/classes/lz/EpConfig.fc | 158 ++ .../ton/src/classes/lz/MsglibInfo.fc | 58 + .../layerzero-v2/ton/src/classes/lz/Packet.fc | 280 +++ .../layerzero-v2/ton/src/classes/lz/Path.fc | 116 ++ .../ton/src/classes/lz/ReceiveEpConfig.fc | 55 + .../ton/src/classes/lz/SendEpConfig.fc | 68 + .../ton/src/classes/lz/SmlJobAssigned.fc | 20 + .../src/classes/lz/tests/UlnReceiveConfig.fc | 92 + .../ton/src/classes/lz/tests/UlnSendConfig.fc | 85 + .../ton/src/classes/lz/tests/main.fc | 93 + .../ton/src/classes/lz/tests/serde.fc | 510 +++++ .../ton/src/classes/msgdata/AddMsglib.fc | 29 + .../ton/src/classes/msgdata/Bool.fc | 16 + .../src/classes/msgdata/ChannelNonceInfo.fc | 38 + .../ton/src/classes/msgdata/CoinsAmount.fc | 24 + .../src/classes/msgdata/CounterIncrement.fc | 24 + .../ton/src/classes/msgdata/Deploy.fc | 51 + .../ton/src/classes/msgdata/ExtendedMd.fc | 71 + .../src/classes/msgdata/GetMsglibCallback.fc | 18 + .../ton/src/classes/msgdata/InitEndpoint.fc | 16 + .../src/classes/msgdata/InitSmlConnection.fc | 16 + .../src/classes/msgdata/LzReceivePrepare.fc | 38 + .../src/classes/msgdata/LzReceiveStatus.fc | 128 ++ .../ton/src/classes/msgdata/LzSend.fc | 186 ++ .../ton/src/classes/msgdata/MdAddress.fc | 60 + .../ton/src/classes/msgdata/MdEid.fc | 26 + .../ton/src/classes/msgdata/MdObj.fc | 58 + .../src/classes/msgdata/MessagingReceipt.fc | 55 + .../src/classes/msgdata/MsglibSendCallback.fc | 195 ++ .../ton/src/classes/msgdata/Nonce.fc | 46 + .../src/classes/msgdata/OptionsExtended.fc | 32 + .../ton/src/classes/msgdata/OptionsV1.fc | 51 + .../ton/src/classes/msgdata/OptionsV2.fc | 64 + .../ton/src/classes/msgdata/PacketId.fc | 42 + .../ton/src/classes/msgdata/PacketSent.fc | 73 + .../ton/src/classes/msgdata/SetAddress.fc | 23 + .../ton/src/classes/msgdata/SetEpConfig.fc | 50 + .../ton/src/classes/msgdata/SetPeer.fc | 18 + .../classes/msgdata/SetSmlManagerConfig.fc | 18 + .../ton/src/classes/msgdata/tests/main.fc | 44 + .../ton/src/classes/msgdata/tests/serde.fc | 956 +++++++++ .../funC++/abstract/contractMainAbstract.fc | 14 + .../src/funC++/abstract/handlerAbstract.fc | 20 + .../ton/src/funC++/actions/RawCall.fc | 43 + .../ton/src/funC++/actions/call.fc | 54 + .../ton/src/funC++/actions/deploy.fc | 117 ++ .../ton/src/funC++/actions/dispatch.fc | 60 + .../ton/src/funC++/actions/event.fc | 89 + .../ton/src/funC++/actions/payment.fc | 52 + .../ton/src/funC++/actions/sendJettons.fc | 75 + .../ton/src/funC++/actions/utils.fc | 47 + .../ton/src/funC++/baseInterface.fc | 15 + .../layerzero-v2/ton/src/funC++/classlib.fc | 867 ++++++++ .../layerzero-v2/ton/src/funC++/constants.fc | 64 + .../ton/src/funC++/contractMain.fc | 81 + .../src/funC++/dataStructures/AddressList.fc | 89 + .../DeterministicInsertionCircularQueue.fc | 154 ++ .../dataStructures/PipelinedOutOfOrder.fc | 132 ++ .../ton/src/funC++/handlerCore.fc | 27 + .../layerzero-v2/ton/src/funC++/stdlib.fc | 640 ++++++ .../layerzero-v2/ton/src/funC++/stringlib.fc | 73 + .../ton/src/funC++/tests/POOO/main.fc | 153 ++ .../ton/src/funC++/tests/POOO/serde.fc | 95 + .../ton/src/funC++/tests/actions/serde.fc | 28 + .../src/funC++/tests/baseContract/autoload.fc | 2 + .../src/funC++/tests/baseContract/handler.fc | 15 + .../ton/src/funC++/tests/baseContract/main.fc | 122 ++ .../src/funC++/tests/baseContract/storage.fc | 15 + .../ton/src/funC++/tests/classlib/main.fc | 517 +++++ .../ton/src/funC++/tests/txnContext/main.fc | 460 ++++ .../ton/src/funC++/tests/util/main.fc | 141 ++ .../layerzero-v2/ton/src/funC++/testutils.fc | 98 + .../layerzero-v2/ton/src/funC++/txnContext.fc | 125 ++ .../layerzero-v2/ton/src/funC++/utils.fc | 323 +++ .../ton/src/jettons/zro/meta.json | 7 + .../ton/src/jettons/zro/minter.fc | 120 ++ .../ton/src/jettons/zro/op-codes.fc | 10 + .../ton/src/jettons/zro/params.fc | 18 + .../layerzero-v2/ton/src/jettons/zro/utils.fc | 33 + .../ton/src/jettons/zro/wallet.fc | 261 +++ .../ton/src/multisig/auto/order_code.func | 7 + .../src/multisig/bocs/MultiSig.compiled.json | 5 + .../multisig/bocs/MultiSigOrder.compiled.json | 5 + .../layerzero-v2/ton/src/multisig/errors.func | 17 + .../ton/src/multisig/imports/stdlib.fc | 882 ++++++++ .../ton/src/multisig/messages.func | 54 + .../ton/src/multisig/multisig.func | 203 ++ .../ton/src/multisig/op-codes.func | 12 + .../layerzero-v2/ton/src/multisig/order.func | 258 +++ .../ton/src/multisig/order_helpers.func | 89 + .../layerzero-v2/ton/src/multisig/types.func | 52 + .../src/protocol/channel/callbackOpcodes.fc | 10 + .../ton/src/protocol/channel/handler.fc | 1108 ++++++++++ .../ton/src/protocol/channel/interface.fc | 61 + .../ton/src/protocol/channel/main.fc | 39 + .../ton/src/protocol/channel/storage.fc | 273 +++ .../src/protocol/channel/tests/channelBurn.fc | 270 +++ .../channel/tests/channelBurnDefaultConfig.fc | 269 +++ .../channel/tests/channelCommitPacket.fc | 729 +++++++ .../protocol/channel/tests/channelConfig.fc | 327 +++ .../channel/tests/channelInitialize.fc | 95 + .../channel/tests/channelMsglibIntegration.fc | 215 ++ .../tests/channelMsglibSendCallback.fc | 492 +++++ .../protocol/channel/tests/channelNilify.fc | 439 ++++ .../tests/channelNilifyDefaultConfig.fc | 438 ++++ .../protocol/channel/tests/channelReceive.fc | 418 ++++ .../channel/tests/channelReceiveCallback.fc | 321 +++ .../channel/tests/channelReceiveUtils.fc | 28 + .../channel/tests/channelReceiveView.fc | 121 ++ .../src/protocol/channel/tests/channelSend.fc | 508 +++++ .../protocol/channel/tests/channelSerde.fc | 368 ++++ .../src/protocol/channel/tests/permissions.fc | 230 ++ .../ton/src/protocol/controller/handler.fc | 390 ++++ .../ton/src/protocol/controller/interface.fc | 25 + .../ton/src/protocol/controller/main.fc | 29 + .../ton/src/protocol/controller/storage.fc | 31 + .../protocol/controller/tests/assertions.fc | 200 ++ .../ton/src/protocol/controller/tests/main.fc | 387 ++++ .../protocol/controller/tests/permissions.fc | 172 ++ .../protocol/core/abstract/protocolHandler.fc | 100 + .../protocol/core/abstract/protocolMain.fc | 30 + .../ton/src/protocol/core/baseStorage.fc | 57 + .../ton/src/protocol/endpoint/handler.fc | 484 +++++ .../ton/src/protocol/endpoint/interface.fc | 20 + .../ton/src/protocol/endpoint/main.fc | 23 + .../ton/src/protocol/endpoint/storage.fc | 123 ++ .../tests/endpointSetEpConfigDefaults.fc | 443 ++++ .../ton/src/protocol/endpoint/tests/main.fc | 668 ++++++ .../protocol/endpoint/tests/permissions.fc | 130 ++ .../ton/src/protocol/endpoint/tests/serde.fc | 167 ++ .../ton/src/protocol/interfaces.fc | 4 + .../ton/src/protocol/msglibs/BytesDecoder.fc | 135 ++ .../ton/src/protocol/msglibs/BytesEncoder.fc | 151 ++ .../ton/src/protocol/msglibs/interface.fc | 22 + .../simpleMsglib/smlConnection/handler.fc | 125 ++ .../simpleMsglib/smlConnection/interface.fc | 3 + .../simpleMsglib/smlConnection/main.fc | 19 + .../simpleMsglib/smlConnection/storage.fc | 20 + .../simpleMsglib/smlConnection/tests/main.fc | 74 + .../smlConnection/tests/permissions.fc | 52 + .../simpleMsglib/smlManager/handler.fc | 278 +++ .../simpleMsglib/smlManager/interface.fc | 14 + .../msglibs/simpleMsglib/smlManager/main.fc | 24 + .../simpleMsglib/smlManager/storage.fc | 38 + .../simpleMsglib/smlManager/tests/main.fc | 202 ++ .../smlManager/tests/permissions.fc | 145 ++ .../msglibs/tests/msglibPacketCodec.fc | 184 ++ .../msglibs/ultralightnode/callbackOpcodes.fc | 1 + .../msglibs/ultralightnode/feeLibInterface.fc | 12 + .../msglibs/ultralightnode/feeLibUtils.fc | 52 + .../ultralightnode/msgdata/Attestation.fc | 48 + .../msgdata/DvnFeesPaidEvent.fc | 46 + .../msgdata/ExecutorFeePaidEvent.fc | 38 + .../msglibs/ultralightnode/msgdata/InitUln.fc | 25 + .../msgdata/InitUlnConnection.fc | 62 + .../ultralightnode/msgdata/InitUlnManager.fc | 18 + .../ultralightnode/msgdata/RentRefill.fc | 18 + .../msgdata/SetAdminWorkerAddresses.fc | 24 + .../ultralightnode/msgdata/TreasuryFeeBps.fc | 16 + .../ultralightnode/msgdata/UlnEvents.fc | 46 + .../msgdata/UlnReceiveConfig.fc | 335 +++ .../msglibs/ultralightnode/msgdata/UlnSend.fc | 77 + .../ultralightnode/msgdata/UlnSendConfig.fc | 169 ++ .../ultralightnode/msgdata/UlnVerification.fc | 31 + .../msgdata/UlnWorkerFeelibBytecode.fc | 22 + .../msgdata/UlnWorkerFeelibEvents.fc | 58 + .../msgdata/UlnWorkerFeelibInfo.fc | 106 + .../msgdata/VerificationStatus.fc | 33 + .../ultralightnode/msgdata/tests/serde.fc | 494 +++++ .../msglibs/ultralightnode/uln/handler.fc | 787 +++++++ .../msglibs/ultralightnode/uln/interface.fc | 63 + .../msglibs/ultralightnode/uln/main.fc | 37 + .../msglibs/ultralightnode/uln/storage.fc | 119 ++ .../uln/tests/badFeeLib/badFeeLib1.fc | 90 + .../uln/tests/badFeeLib/badFeeLib10.fc | 90 + .../uln/tests/badFeeLib/badFeeLib11.fc | 90 + .../uln/tests/badFeeLib/badFeeLib12.fc | 87 + .../uln/tests/badFeeLib/badFeeLib2.fc | 91 + .../uln/tests/badFeeLib/badFeeLib3.fc | 96 + .../uln/tests/badFeeLib/badFeeLib4.fc | 90 + .../uln/tests/badFeeLib/badFeeLib5.fc | 90 + .../uln/tests/badFeeLib/badFeeLib6.fc | 91 + .../uln/tests/badFeeLib/badFeeLib7.fc | 90 + .../uln/tests/badFeeLib/badFeeLib8.fc | 90 + .../uln/tests/badFeeLib/badFeeLib9.fc | 90 + .../computeSizeGasTest/computeSizeGasTest.fc | 62 + .../uln/tests/computeSizeGasTest/storage.fc | 19 + .../msglibs/ultralightnode/uln/tests/main.fc | 1159 +++++++++++ .../ultralightnode/uln/tests/management.fc | 179 ++ .../ultralightnode/uln/tests/mockWorker.fc | 12 + .../ultralightnode/uln/tests/permissions.fc | 254 +++ .../msglibs/ultralightnode/uln/tests/serde.fc | 133 ++ .../ultralightnode/uln/tests/testutil.fc | 354 ++++ .../ultralightnode/uln/tests/ulnSend.fc | 325 +++ .../uln/tests/ulnSendWithArbDvnFeeLib.fc | 111 + .../uln/tests/ulnSendWithArbExecFeeLib.fc | 157 ++ .../uln/tests/ulnSendWithDefaultDvnFeeLib.fc | 111 + .../uln/tests/ulnSendWithDefaultExecFeeLib.fc | 198 ++ .../uln/tests/ulnSendWithOpDvnFeeLib.fc | 111 + .../uln/tests/ulnSendWithOpExecFeeLib.fc | 157 ++ .../msglibs/ultralightnode/uln/tests/util.fc | 44 + .../ultralightnode/ulnConnection/handler.fc | 639 ++++++ .../ultralightnode/ulnConnection/interface.fc | 36 + .../ultralightnode/ulnConnection/main.fc | 32 + .../ultralightnode/ulnConnection/storage.fc | 156 ++ .../ulnConnection/tests/main.fc | 1851 +++++++++++++++++ .../ulnConnection/tests/permissions.fc | 246 +++ .../ulnConnection/tests/serde.fc | 211 ++ .../ultralightnode/ulnConnection/utils.fc | 52 + .../ultralightnode/ulnManager/handler.fc | 529 +++++ .../ultralightnode/ulnManager/interface.fc | 53 + .../msglibs/ultralightnode/ulnManager/main.fc | 40 + .../ultralightnode/ulnManager/storage.fc | 44 + .../ultralightnode/ulnManager/tests/main.fc | 664 ++++++ .../ulnManager/tests/permissions.fc | 290 +++ .../ultralightnode/ulnManager/tests/util.fc | 44 + .../ultralightnode/workerFeeLibs/common.fc | 26 + .../workerFeeLibs/dvnFeeLib/handler.fc | 110 + .../workerFeeLibs/dvnFeeLib/main.fc | 4 + .../workerFeeLibs/dvnFeeLib/storage.fc | 49 + .../workerFeeLibs/dvnFeeLib/tests/serde.fc | 52 + .../workerFeeLibs/executorFeeLib/handler.fc | 175 ++ .../workerFeeLibs/executorFeeLib/main.fc | 4 + .../workerFeeLibs/executorFeeLib/storage.fc | 48 + .../executorFeeLib/tests/serde.fc | 46 + .../ultralightnode/workerFeeLibs/main.fc | 3 + .../workerFeeLibs/maliciousFeeLib/handler.fc | 9 + .../workerFeeLibs/maliciousFeeLib/main.fc | 3 + .../priceFeedFeeLib/arbitrum/handler.fc | 38 + .../priceFeedFeeLib/arbitrum/main.fc | 2 + .../priceFeedFeeLib/arbitrum/tests/main.fc | 16 + .../priceFeedFeeLib/default/handler.fc | 27 + .../priceFeedFeeLib/default/main.fc | 2 + .../priceFeedFeeLib/default/tests/main.fc | 16 + .../extensions/ArbitrumPriceFeedExtension.fc | 27 + .../priceFeedFeeLib/interface.fc | 1 + .../priceFeedFeeLib/optimism/handler.fc | 45 + .../priceFeedFeeLib/optimism/main.fc | 2 + .../priceFeedFeeLib/optimism/tests/main.fc | 16 + .../workerFeeLibs/priceFeedFeeLib/storage.fc | 161 ++ .../priceFeedFeeLib/tests/serde.fc | 227 ++ .../workerFeeLibs/tests/main.fc | 832 ++++++++ .../workerFeeLibs/ulnSendWorkerV1.fc | 363 ++++ .../workers/core/abstract/workerHandler.fc | 202 ++ .../ton/src/workers/core/interface.fc | 31 + .../src/workers/core/tests/dummyHandler.fc | 45 + .../ton/src/workers/core/tests/main.fc | 220 ++ .../ton/src/workers/core/tests/serde.fc | 37 + .../core/tests/workerStorageTestUtils.fc | 11 + .../ton/src/workers/core/workerCoreStorage.fc | 59 + .../ton/src/workers/dvn/handler.fc | 405 ++++ .../ton/src/workers/dvn/interface.fc | 23 + .../layerzero-v2/ton/src/workers/dvn/main.fc | 38 + .../ton/src/workers/dvn/storage.fc | 40 + .../ton/src/workers/dvn/tests/main.fc | 160 ++ .../ton/src/workers/dvn/tests/permissions.fc | 225 ++ .../ton/src/workers/dvn/tests/serde.fc | 47 + .../ton/src/workers/executor/handler.fc | 160 ++ .../ton/src/workers/executor/interface.fc | 24 + .../ton/src/workers/executor/main.fc | 34 + .../ton/src/workers/executor/storage.fc | 26 + .../ton/src/workers/executor/tests/main.fc | 486 +++++ .../src/workers/executor/tests/permissions.fc | 237 +++ .../ton/src/workers/executor/tests/serde.fc | 31 + .../ton/src/workers/msgdata/ClaimTon.fc | 18 + .../ton/src/workers/msgdata/ExecuteParams.fc | 92 + .../ton/src/workers/msgdata/NativeDrop.fc | 24 + .../ton/src/workers/msgdata/SetDict.fc | 22 + .../ton/src/workers/msgdata/SetQuorum.fc | 22 + .../ton/src/workers/msgdata/SignedRequest.fc | 26 + .../ton/src/workers/msgdata/tests/serde.fc | 171 ++ .../ton/src/workers/priceFeedCache/handler.fc | 228 ++ .../src/workers/priceFeedCache/interface.fc | 7 + .../ton/src/workers/priceFeedCache/main.fc | 31 + .../ton/src/workers/priceFeedCache/storage.fc | 38 + .../src/workers/priceFeedCache/tests/main.fc | 308 +++ .../priceFeedCache/tests/permissions.fc | 308 +++ .../src/workers/priceFeedCache/tests/serde.fc | 59 + .../ton/src/workers/proxy/handler.fc | 84 + .../ton/src/workers/proxy/interface.fc | 12 + .../ton/src/workers/proxy/main.fc | 19 + .../ton/src/workers/proxy/storage.fc | 21 + .../ton/src/workers/proxy/tests/main.fc | 211 ++ .../src/workers/proxy/tests/permissions.fc | 194 ++ .../layerzero-v2/ton/tests/UnitTest.ts | 52 + .../ton/tests/baseContractTest.fc | 204 ++ .../layerzero-v2/ton/tests/baseSerdeTest.fc | 117 ++ .../packages/layerzero-v2/ton/tests/consts.fc | 160 ++ .../ton/tests/globalTestResults.ts | 1 + .../packages/layerzero-v2/ton/tests/mocks.fc | 980 +++++++++ .../ton/tests/protocolStorageTestUtils.fc | 27 + .../ton/tests/testInitTxnContextMain.fc | 89 + .../layerzero-v2/ton/tests/testMain.fc | 134 ++ .../layerzero-v2/ton/tests/unittest.spec.ts | 231 ++ .../packages/layerzero-v2/ton/tsconfig.json | 12 + .../ton/wrappers/ActionsSerde.test.compile.ts | 6 + .../ton/wrappers/AllStorages.compile.ts | 6 + .../ton/wrappers/AllStorages.test.compile.ts | 6 + .../ton/wrappers/BaseContract.test.compile.ts | 6 + .../ton/wrappers/Channel.compile.ts | 6 + .../Channel.permissions.test.compile.ts | 6 + .../ton/wrappers/ChannelBurn.test.compile.ts | 6 + .../ChannelBurnDefaultConfig.test.compile.ts | 6 + .../ChannelCommitPacket.test.compile.ts | 6 + .../wrappers/ChannelConfig.test.compile.ts | 6 + .../ChannelInitialize.test.compile.ts | 6 + .../ChannelMsglibIntegration.test.compile.ts | 6 + .../ChannelMsglibSendCallback.test.compile.ts | 6 + .../wrappers/ChannelNilify.test.compile.ts | 6 + ...ChannelNilifyDefaultConfig.test.compile.ts | 6 + .../wrappers/ChannelReceive.test.compile.ts | 6 + .../ChannelReceiveCallback.test.compile.ts | 6 + .../ChannelReceiveView.test.compile.ts | 6 + .../ton/wrappers/ChannelSend.test.compile.ts | 6 + .../ton/wrappers/ChannelSerde.test.compile.ts | 6 + .../ton/wrappers/Classlib.test.compile.ts | 6 + .../ComputeDataSizeGas.test.compile.ts | 6 + .../ton/wrappers/Connection.compile.ts | 6 + .../Controller.assertions.test.compile.ts | 6 + .../ton/wrappers/Controller.compile.ts | 6 + .../Controller.permissions.test.compile.ts | 6 + .../ton/wrappers/Controller.test.compile.ts | 6 + .../ton/wrappers/Counter.compile.ts | 6 + .../Counter.permissions.test.compile.ts | 6 + .../wrappers/Counter.setters.test.compile.ts | 6 + .../ton/wrappers/Counter.test.compile.ts | 6 + .../layerzero-v2/ton/wrappers/Dvn.compile.ts | 6 + .../ton/wrappers/Dvn.test.compile.ts | 6 + .../ton/wrappers/DvnFeeLib.compile.ts | 6 + .../wrappers/DvnFeeLibSerde.test.compile.ts | 6 + .../wrappers/DvnPermissions.test.compile.ts | 6 + .../ton/wrappers/DvnSerde.test.compile.ts | 6 + .../ton/wrappers/Endpoint.compile.ts | 6 + .../Endpoint.permissions.test.compile.ts | 6 + .../ton/wrappers/Endpoint.test.compile.ts | 6 + .../wrappers/EndpointSerde.test.compile.ts | 6 + ...ndpointSetEpConfigDefaults.test.compile.ts | 6 + .../ton/wrappers/Executor.compile.ts | 6 + .../ton/wrappers/Executor.test.compile.ts | 6 + .../ton/wrappers/ExecutorFeeLib.compile.ts | 6 + .../ExecutorFeeLibSerde.test.compile.ts | 6 + .../ExecutorPermissions.test.compile.ts | 6 + .../wrappers/ExecutorSerde.test.compile.ts | 6 + .../ton/wrappers/LzClasses.test.compile.ts | 6 + .../wrappers/LzClassesSerde.test.compile.ts | 6 + .../ton/wrappers/LzUtil.test.compile.ts | 6 + .../ton/wrappers/MaliciousFeeLib.compile.ts | 6 + .../ton/wrappers/MsgData.test.compile.ts | 6 + .../ton/wrappers/MsgDataSerde.test.compile.ts | 6 + .../MsglibPacketCodec.test.compile.ts | 6 + .../ton/wrappers/MultiSig.compile.ts | 6 + .../ton/wrappers/MultiSigOrder.compile.ts | 6 + .../PipelinedOutOfOrder.test.compile.ts | 6 + .../PipelinedOutOfOrderSerde.test.compile.ts | 6 + .../ton/wrappers/PriceFeedCache.compile.ts | 6 + .../wrappers/PriceFeedCache.test.compile.ts | 6 + ...PriceFeedCache.test.permissions.compile.ts | 6 + .../PriceFeedCacheSerde.test.compile.ts | 6 + .../PriceFeedFeeLibArbitrum.compile.ts | 6 + .../PriceFeedFeeLibArbitrum.test.compile.ts | 6 + .../PriceFeedFeeLibDefault.compile.ts | 6 + .../PriceFeedFeeLibDefault.test.compile.ts | 6 + .../PriceFeedFeeLibOptimism.compile.ts | 6 + .../PriceFeedFeeLibOptimism.test.compile.ts | 6 + .../PriceFeedFeeLibSerde.test.compile.ts | 6 + .../ton/wrappers/Proxy.compile.ts | 6 + .../Proxy.permissions.test.compile.ts | 6 + .../ton/wrappers/Proxy.test.compile.ts | 6 + .../ton/wrappers/SmlConnection.compile.ts | 6 + .../SmlConnection.permissions.test.compile.ts | 6 + .../wrappers/SmlConnection.test.compile.ts | 6 + .../ton/wrappers/SmlManager.compile.ts | 6 + .../SmlManager.permissions.test.compile.ts | 6 + .../ton/wrappers/SmlManager.test.compile.ts | 6 + .../ton/wrappers/TxnContext.test.compile.ts | 6 + .../layerzero-v2/ton/wrappers/Uln.compile.ts | 6 + .../ton/wrappers/Uln.test.compile.ts | 6 + .../ton/wrappers/UlnConnection.compile.ts | 6 + .../wrappers/UlnConnection.test.compile.ts | 6 + .../UlnConnectionPermissions.test.compile.ts | 6 + .../UlnConnectionSerde.test.compile.ts | 6 + .../wrappers/UlnManagement.test.compile.ts | 6 + .../ton/wrappers/UlnManager.compile.ts | 6 + .../ton/wrappers/UlnManager.test.compile.ts | 6 + .../UlnManagerPermissions.test.compile.ts | 6 + .../wrappers/UlnManagerUtil.test.compile.ts | 6 + .../wrappers/UlnMsgDataSerde.test.compile.ts | 6 + .../wrappers/UlnPermissions.test.compile.ts | 6 + .../wrappers/UlnReceiveConfig.test.compile.ts | 6 + .../ton/wrappers/UlnSend.test.compile.ts | 6 + .../wrappers/UlnSendConfig.test.compile.ts | 6 + .../UlnSendWithArbDvnFeeLib.test.compile.ts | 6 + .../UlnSendWithArbExecFeeLib.test.compile.ts | 6 + ...lnSendWithDefaultDvnFeeLib.test.compile.ts | 6 + ...nSendWithDefaultExecFeeLib.test.compile.ts | 6 + .../UlnSendWithOpDvnFeeLib.test.compile.ts | 6 + .../UlnSendWithOpExecFeeLib.test.compile.ts | 6 + .../UlnSendWorkerFactory.test.compile.ts | 6 + .../ton/wrappers/UlnSerde.test.compile.ts | 6 + .../ton/wrappers/UlnUtil.test.compile.ts | 6 + .../ton/wrappers/WorkerCore.test.compile.ts | 6 + .../WorkerCoreMsgDataSerde.test.compile.ts | 6 + .../wrappers/WorkerCoreSerde.test.compile.ts | 6 + .../ton/wrappers/ZroMinter.compile.ts | 6 + .../ton/wrappers/ZroWallet.compile.ts | 6 + .../ton/wrappers/badFeeLib1.test.compile.ts | 6 + .../ton/wrappers/badFeeLib10.test.compile.ts | 6 + .../ton/wrappers/badFeeLib11.test.compile.ts | 6 + .../ton/wrappers/badFeeLib12.test.compile.ts | 6 + .../ton/wrappers/badFeeLib2.test.compile.ts | 6 + .../ton/wrappers/badFeeLib3.test.compile.ts | 6 + .../ton/wrappers/badFeeLib4.test.compile.ts | 6 + .../ton/wrappers/badFeeLib5.test.compile.ts | 6 + .../ton/wrappers/badFeeLib6.test.compile.ts | 6 + .../ton/wrappers/badFeeLib7.test.compile.ts | 6 + .../ton/wrappers/badFeeLib8.test.compile.ts | 6 + .../ton/wrappers/badFeeLib9.test.compile.ts | 6 + lib/devtools/.changeset/pink-chairs-double.md | 5 + lib/devtools/.github/USAGE.md | 23 + .../examples/oapp-aptos-move/CHANGELOG.md | 13 + .../oft-adapter-aptos-move/CHANGELOG.md | 7 + .../examples/oft-aptos-move/CHANGELOG.md | 7 + .../devtools-extensible-cli/CHANGELOG.md | 13 + .../packages/devtools-move/CHANGELOG.md | 13 + .../packages/oapp-alt-evm/CHANGELOG.md | 7 + .../packages/oft-alt-evm/CHANGELOG.md | 7 + lib/devtools/packages/oft-move/CHANGELOG.md | 13 + .../packages/test-devtools-ton/CHANGELOG.md | 7 + lib/forge-std/CONTRIBUTING.md | 193 ++ lib/forge-std/src/interfaces/IERC7540.sol | 150 ++ lib/forge-std/src/interfaces/IERC7575.sol | 241 +++ .../.changeset/fast-coats-try.md | 5 + .../.changeset/good-cameras-rush.md | 5 + .../.changeset/gorgeous-apes-jam.md | 5 + .../.changeset/sixty-tips-wink.md | 5 + lib/openzeppelin-contracts/.githooks/pre-push | 8 + lib/openzeppelin-contracts/scripts/prepare.sh | 5 + .../test/helpers/erc4337-entrypoint.js | 31 + .../test/helpers/precompiles.js | 12 + lib/solady/prep/memory-safe-scan.js | 29 + lib/solady/src/accounts/LibEIP7702.sol | 150 ++ .../SystemConfigManager.sol | 5 +- .../batchDeploymentScript/batchDeploy.s.sol | 17 +- src/RVaultAsset.sol | 21 +- src/Router.sol | 6 +- src/interfaces/ICrossL2Prover.sol | 44 +- src/libraries/EventValidator.sol | 76 +- test/Base.t.sol | 7 +- 470 files changed, 47498 insertions(+), 86 deletions(-) create mode 100644 deployment.json create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/.gitignore create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/jest.config.js create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/package.json create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/class_autoload.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/oAppMain.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/autoload.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/setters.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Config.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/EpConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/MsglibInfo.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Packet.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Path.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/ReceiveEpConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/SendEpConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/SmlJobAssigned.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/UlnReceiveConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/UlnSendConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/AddMsglib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Bool.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/ChannelNonceInfo.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/CoinsAmount.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/CounterIncrement.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Deploy.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/ExtendedMd.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/GetMsglibCallback.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/InitEndpoint.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/InitSmlConnection.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzReceivePrepare.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzReceiveStatus.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzSend.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdAddress.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdEid.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdObj.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MessagingReceipt.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MsglibSendCallback.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Nonce.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsExtended.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsV1.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsV2.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/PacketId.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/PacketSent.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetAddress.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetEpConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetPeer.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetSmlManagerConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/abstract/contractMainAbstract.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/abstract/handlerAbstract.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/RawCall.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/call.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/deploy.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/dispatch.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/event.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/payment.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/sendJettons.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/utils.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/baseInterface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/classlib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/constants.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/contractMain.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/AddressList.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/DeterministicInsertionCircularQueue.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/PipelinedOutOfOrder.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/handlerCore.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/stdlib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/stringlib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/POOO/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/POOO/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/actions/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/autoload.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/classlib/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/txnContext/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/util/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/testutils.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/txnContext.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/utils.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/meta.json create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/minter.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/op-codes.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/params.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/utils.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/wallet.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/auto/order_code.func create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/bocs/MultiSig.compiled.json create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/bocs/MultiSigOrder.compiled.json create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/errors.func create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/imports/stdlib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/messages.func create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/multisig.func create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/op-codes.func create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/order.func create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/order_helpers.func create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/types.func create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/callbackOpcodes.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelBurn.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelBurnDefaultConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelCommitPacket.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelInitialize.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelMsglibIntegration.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelMsglibSendCallback.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelNilify.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelNilifyDefaultConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceive.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveCallback.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveUtils.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveView.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelSend.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelSerde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/assertions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/abstract/protocolHandler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/abstract/protocolMain.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/baseStorage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/endpointSetEpConfigDefaults.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/interfaces.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/BytesDecoder.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/BytesEncoder.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/tests/msglibPacketCodec.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/callbackOpcodes.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/feeLibInterface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/feeLibUtils.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/Attestation.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/DvnFeesPaidEvent.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/ExecutorFeePaidEvent.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUln.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUlnConnection.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUlnManager.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/RentRefill.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/SetAdminWorkerAddresses.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/TreasuryFeeBps.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnEvents.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnReceiveConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnSend.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnSendConfig.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnVerification.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibBytecode.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibEvents.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibInfo.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/VerificationStatus.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib1.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib10.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib11.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib12.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib2.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib3.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib4.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib5.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib6.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib7.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib8.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib9.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/computeSizeGasTest.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/management.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/mockWorker.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/testutil.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSend.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbDvnFeeLib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbExecFeeLib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultDvnFeeLib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultExecFeeLib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpDvnFeeLib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpExecFeeLib.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/util.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/utils.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/util.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/common.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/maliciousFeeLib/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/maliciousFeeLib/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/extensions/ArbitrumPriceFeedExtension.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/ulnSendWorkerV1.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/abstract/workerHandler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/dummyHandler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/workerStorageTestUtils.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/workerCoreStorage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/ClaimTon.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/ExecuteParams.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/NativeDrop.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SetDict.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SetQuorum.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SignedRequest.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/serde.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/handler.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/interface.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/storage.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/tests/main.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/tests/permissions.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/UnitTest.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/baseContractTest.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/baseSerdeTest.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/consts.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/globalTestResults.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/mocks.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/protocolStorageTestUtils.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/testInitTxnContextMain.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/testMain.fc create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tests/unittest.spec.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/tsconfig.json create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ActionsSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/AllStorages.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/AllStorages.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/BaseContract.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Channel.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Channel.permissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelBurn.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelBurnDefaultConfig.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelCommitPacket.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelConfig.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelInitialize.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelMsglibIntegration.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelMsglibSendCallback.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelNilify.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelNilifyDefaultConfig.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceive.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceiveCallback.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceiveView.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelSend.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Classlib.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ComputeDataSizeGas.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Connection.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.assertions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.permissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.permissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.setters.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Dvn.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Dvn.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnFeeLib.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnFeeLibSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnPermissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.permissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/EndpointSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/EndpointSetEpConfigDefaults.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Executor.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Executor.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorFeeLib.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorFeeLibSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorPermissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzClasses.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzClassesSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzUtil.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MaliciousFeeLib.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsgData.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsgDataSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsglibPacketCodec.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MultiSig.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MultiSigOrder.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PipelinedOutOfOrder.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PipelinedOutOfOrderSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.test.permissions.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCacheSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibArbitrum.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibArbitrum.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibDefault.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibDefault.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibOptimism.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibOptimism.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.permissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.permissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.permissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/TxnContext.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Uln.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Uln.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnection.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnection.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnectionPermissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnectionSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagement.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManager.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManager.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagerPermissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagerUtil.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnMsgDataSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnPermissions.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnReceiveConfig.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSend.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendConfig.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithArbDvnFeeLib.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithArbExecFeeLib.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithDefaultDvnFeeLib.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithDefaultExecFeeLib.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithOpDvnFeeLib.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithOpExecFeeLib.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWorkerFactory.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnUtil.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCore.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCoreMsgDataSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCoreSerde.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ZroMinter.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ZroWallet.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib1.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib10.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib11.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib12.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib2.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib3.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib4.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib5.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib6.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib7.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib8.test.compile.ts create mode 100644 lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib9.test.compile.ts create mode 100644 lib/devtools/.changeset/pink-chairs-double.md create mode 100644 lib/devtools/.github/USAGE.md create mode 100644 lib/devtools/examples/oapp-aptos-move/CHANGELOG.md create mode 100644 lib/devtools/examples/oft-adapter-aptos-move/CHANGELOG.md create mode 100644 lib/devtools/examples/oft-aptos-move/CHANGELOG.md create mode 100644 lib/devtools/packages/devtools-extensible-cli/CHANGELOG.md create mode 100644 lib/devtools/packages/devtools-move/CHANGELOG.md create mode 100644 lib/devtools/packages/oapp-alt-evm/CHANGELOG.md create mode 100644 lib/devtools/packages/oft-alt-evm/CHANGELOG.md create mode 100644 lib/devtools/packages/oft-move/CHANGELOG.md create mode 100644 lib/devtools/packages/test-devtools-ton/CHANGELOG.md create mode 100644 lib/forge-std/CONTRIBUTING.md create mode 100644 lib/forge-std/src/interfaces/IERC7540.sol create mode 100644 lib/forge-std/src/interfaces/IERC7575.sol create mode 100644 lib/openzeppelin-contracts/.changeset/fast-coats-try.md create mode 100644 lib/openzeppelin-contracts/.changeset/good-cameras-rush.md create mode 100644 lib/openzeppelin-contracts/.changeset/gorgeous-apes-jam.md create mode 100644 lib/openzeppelin-contracts/.changeset/sixty-tips-wink.md create mode 100644 lib/openzeppelin-contracts/.githooks/pre-push create mode 100644 lib/openzeppelin-contracts/scripts/prepare.sh create mode 100644 lib/openzeppelin-contracts/test/helpers/erc4337-entrypoint.js create mode 100644 lib/openzeppelin-contracts/test/helpers/precompiles.js create mode 100644 lib/solady/prep/memory-safe-scan.js create mode 100644 lib/solady/src/accounts/LibEIP7702.sol diff --git a/.gitignore b/.gitignore index 69596980..76d4d5c4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,11 +11,8 @@ docs/ # Dotenv file .env -deployment.json node_modules/ .specstory/ .gitpod.yml pnpm-lock.yaml count_lines.py -lib -lib/ \ No newline at end of file diff --git a/configs/deploy-config.toml b/configs/deploy-config.toml index 4fc21cd8..2737cb36 100644 --- a/configs/deploy-config.toml +++ b/configs/deploy-config.toml @@ -1,7 +1,7 @@ [deploy_config] chains = ["eth_mainnet","op_mainnet","sepolia-eth"] [owner] -address = "0x5377679614bc0BB997d82D11D79A87e3c5695848" +address = "0xcbc771976e3623ad236A4D8556897C47925B2Aaa" [underlying] # riftlendUnderlying @@ -49,24 +49,24 @@ lpType="OpSuperchain_LENDING_POOL" salt = "riftlendProxyAdmin" [pool_admin] -address="0x5377679614bc0BB997d82D11D79A87e3c5695848" +address="0xcbc771976e3623ad236A4D8556897C47925B2Aaa" [lending_pool] # riftlendLendingPool -salt = "riftlendLendingPool1" +salt = "riftlendLendingPool" [lending_pool_configurator] # riftlendLpConfigurator -salt = "riftlendLpConfigurator1" +salt = "riftlendLpConfigurator11" [lending_pool_collateral_manager] # riftlendLpCollateralManager -salt = "riftlendLpCollateralManager1" +salt = "riftlendLpCollateralManager11" [default_reserve_interest_rate_strategy] # riftlendDefaultStrategy -salt = "riftlendDefaultStrategy" +salt = "riftlendDefaultStrategy1" # These are random values. TODO these should be in ray terms optimalUtilizationRate = 1 baseVariableBorrowRate = 2 @@ -78,7 +78,7 @@ stableRateSlope2 = 5 [price_oracle] # riftlendPriceOracle salt = "riftlendPriceOracle" -owner_address = "0x5377679614bc0BB997d82D11D79A87e3c5695848" +owner_address = "0xcbc771976e3623ad236A4D8556897C47925B2Aaa" [event_validator] # riftlendEventValidator @@ -94,7 +94,7 @@ address6="0x2EEB89646C0fC522980f08a6DF6f9843fefFBA77" total=6 [treasury] -address = "0x5377679614bc0BB997d82D11D79A87e3c5695848" +address = "0xcbc771976e3623ad236A4D8556897C47925B2Aaa" [incentives_controller] address = "0x0000000000000000000000000000000000000000" @@ -111,21 +111,21 @@ lz_compose_gas_limit=500000 [forks] chain_a_rpc_url = "https://eth-mainnet.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3" chain_a_chain_id = 1 -chain_a_cross_l2_prover_address="0x1CCb363c18484A568DCB0Ec37fE5ad716C1D6e77" +chain_a_cross_l2_prover_address="0x441f16587d8a8cACE647352B24E1Aefa55ACEA76" chain_a_weth="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" chain_a_lz_endpoint_v2="0x1a44076050125825900e736c501f859c50fE728c" chain_a_lz_delegate="0xf9E5bFE615853BC0ce310Dd3187277B0bbAC112c" chain_b_rpc_url = "https://opt-mainnet.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3" chain_b_chain_id = 10 -chain_b_cross_l2_prover_address="0x1CCb363c18484A568DCB0Ec37fE5ad716C1D6e77" +chain_b_cross_l2_prover_address="0x441f16587d8a8cACE647352B24E1Aefa55ACEA76" chain_b_weth="0x4200000000000000000000000000000000000006" chain_b_lz_endpoint_v2="0x1a44076050125825900e736c501f859c50fE728c" chain_b_lz_delegate="0xf9E5bFE615853BC0ce310Dd3187277B0bbAC112c" chain_test_a_rpc_url = "https://opt-sepolia.g.alchemy.com/v2/muWMumYKuC6W7Oa572HjryTPZMMLbPK3" chain_test_a_chain_id = 11155420 -chain_test_a_cross_l2_prover_address="0xb8AcB3FE3117A67b665Bc787c977623612f8a461" +chain_test_a_cross_l2_prover_address="0xcDa03d74DEc5B24071D1799899B2e0653C24e5Fa" chain_test_a_weth="0xC355F97a3Aa2808Acb536E555764b8FB26fB4b37" chain_test_a_lz_endpoint_v2="0x6EDCE65403992e310A62460808c4b910D972f10f" chain_test_a_lz_delegate="0xf9E5bFE615853BC0ce310Dd3187277B0bbAC112c" diff --git a/deployment.json b/deployment.json new file mode 100644 index 00000000..37d4ddbc --- /dev/null +++ b/deployment.json @@ -0,0 +1,14 @@ +{ + "LendingPool": "0x268e0e09A14Ca083a4b06B6823D8385D442d1540", + "LendingPoolAddressesProvider": "0x5eF6b4D77899533eBB6ED81a05834f43E1e847eD", + "LendingPoolCollateralManager": "0x73d4c6d46AE8ea3748A12f09aCCc091DF72AeAf0", + "LendingPoolConfigurator": "0xb6d729c8b6E77a27080438Dc5ed4449Da9625C50", + "Owner": "0xcbc771976e3623ad236A4D8556897C47925B2Aaa", + "ProxyAdmin": "0xa326e831b46305A71e5d60d9b7Beba966D750adc", + "RToken": "0xc96227c987c4bfb65C0059e7d229aB068B17B270", + "RVaultAsset": "0x9A425CF7DaB7D53c142a623BeDc166F11bfe1322", + "Router": "0x98C1172Ad479d2860b884d2a8250CaE19B29b8AA", + "SuperAsset": "0x85946bD52Eaa185Fe52e0700b4Aad140aE7B1F23", + "Underlying": "0x586ee4C29f75a83c8f4BE910B073b246369A231C", + "VariableDebtToken": "0x56E13C273f0E926183b4BcE9d669bB585e8645f1" +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/.gitignore b/lib/LayerZero-v2/packages/layerzero-v2/ton/.gitignore new file mode 100644 index 00000000..d0ee4463 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/.gitignore @@ -0,0 +1,3 @@ +*.fif +*.boc +build/ \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/jest.config.js b/lib/LayerZero-v2/packages/layerzero-v2/ton/jest.config.js new file mode 100644 index 00000000..381d4eff --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/jest.config.js @@ -0,0 +1,8 @@ +// jest.config.js (after) +/** @type {import('jest').Config} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['/node_modules/', '/dist/'], + testTimeout: 600 * 1000, +}; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/package.json b/lib/LayerZero-v2/packages/layerzero-v2/ton/package.json new file mode 100644 index 00000000..ef528a55 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/package.json @@ -0,0 +1,27 @@ +{ + "name": "@layerzerolabs/layerzero-v2-ton", + "private": true, + "license": "LZBL-1.2", + "scripts": { + "build": "$npm_execpath clean-prebuild && $npm_execpath blueprint build --all", + "clean-prebuild": "rimraf target", + "test": "$npm_execpath jest --verbose" + }, + "devDependencies": { + "@jest/globals": "^29.7.0", + "@ston-fi/funcbox": "^0.1.1", + "@ton/blueprint": "0.25.0-beta.1", + "@ton/core": "^0.59.0", + "@ton/crypto": "^3.3.0", + "@ton/sandbox": "^0.22.0", + "@ton/test-utils": "^0.4.2", + "@ton/ton": "15.1.0", + "@types/jest": "^29.5.10", + "@types/node": "^20.10.5", + "jest": "^29.7.0", + "rimraf": "^5.0.5", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "typescript": "~5.2.2" + } +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/class_autoload.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/class_autoload.fc new file mode 100644 index 00000000..6d27cba9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/class_autoload.fc @@ -0,0 +1,49 @@ +#include "../../classes/msgdata/CounterIncrement.fc"; +#include "../../classes/msgdata/Deploy.fc"; +#include "../../classes/msgdata/ExtendedMd.fc"; +#include "../../classes/msgdata/GetMsglibCallback.fc"; +#include "../../classes/msgdata/InitEndpoint.fc"; +#include "../../classes/msgdata/InitSmlConnection.fc"; +#include "../../classes/msgdata/LzReceiveStatus.fc"; +#include "../../classes/msgdata/LzReceivePrepare.fc"; +#include "../../classes/msgdata/LzSend.fc"; +#include "../../classes/msgdata/MdAddress.fc"; +#include "../../classes/msgdata/MdObj.fc"; +#include "../../classes/msgdata/MdEid.fc"; +#include "../../classes/msgdata/MessagingReceipt.fc"; +#include "../../classes/msgdata/MsglibSendCallback.fc"; +#include "../../classes/msgdata/Nonce.fc"; +#include "../../classes/msgdata/OptionsV1.fc"; +#include "../../classes/msgdata/OptionsExtended.fc"; +#include "../../classes/msgdata/PacketId.fc"; +#include "../../classes/msgdata/PacketSent.fc"; +#include "../../classes/msgdata/AddMsglib.fc"; +#include "../../classes/msgdata/SetAddress.fc"; +#include "../../classes/msgdata/SetPeer.fc"; +#include "../../classes/msgdata/SetSmlManagerConfig.fc"; +#include "../../classes/msgdata/SetEpConfig.fc"; + +#include "../../protocol/msglibs/ultralightnode/msgdata/Attestation.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/DvnFeesPaidEvent.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/ExecutorFeePaidEvent.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/InitUln.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/InitUlnConnection.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/InitUlnManager.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnEvents.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnReceiveConfig.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnSend.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnSendConfig.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnVerification.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibBytecode.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibEvents.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibInfo.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/VerificationStatus.fc"; + +#include "../../classes/lz/Config.fc"; +#include "../../classes/lz/EpConfig.fc"; +#include "../../classes/lz/MsglibInfo.fc"; +#include "../../classes/lz/Packet.fc"; +#include "../../classes/lz/Path.fc"; +#include "../../classes/lz/ReceiveEpConfig.fc"; +#include "../../classes/lz/SendEpConfig.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnReceiveConfig.fc"; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/main.fc new file mode 100644 index 00000000..29c56546 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/main.fc @@ -0,0 +1,116 @@ +#include "../../protocol/msglibs/BytesEncoder.fc"; +#include "../../protocol/msglibs/BytesDecoder.fc"; +#include "../../protocol/controller/storage.fc"; +#include "../../protocol/endpoint/storage.fc"; +#include "../../protocol/channel/storage.fc"; +#include "../../protocol/msglibs/simpleMsglib/smlManager/storage.fc"; +#include "../../protocol/msglibs/simpleMsglib/smlConnection/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/uln/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/ulnConnection/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/ulnManager/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/storage.fc"; + +#include "../../protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/uln/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/ulnManager/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/storage.fc"; +#include "../../protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/storage.fc"; + +#include "../../workers/core/workerCoreStorage.fc"; +#include "../../workers/dvn/storage.fc"; +#include "../../workers/executor/storage.fc"; +#include "../../workers/priceFeedCache/storage.fc"; +#include "../../workers/proxy/storage.fc"; + +#include "../../workers/msgdata/ExecuteParams.fc"; +#include "../../workers/msgdata/NativeDrop.fc"; +#include "../../workers/msgdata/SetDict.fc"; +#include "../../workers/msgdata/SetQuorum.fc"; +#include "../../workers/msgdata/SignedRequest.fc"; +#include "../../workers/msgdata/ClaimTon.fc"; +#include "../../funC++/actions/utils.fc"; +#include "../counter/storage.fc"; +#include "./class_autoload.fc"; + +;;; =========================== All Storages =================================== + ;; this contract is purely for convenience during unit testing and deployment + ;; to expose view functions + +(cell, tuple) _initialize(cell $md) impure inline { + return (getContractStorage(), emptyActions()); +} + +int getCellBits(cell input) method_id { + (_, int bits, _) = compute_data_size(input, MAX_U16); + return bits; +} + +() main() impure { } + +;; integration helper function returns lz::Packet +cell decodeAndOverrideNonce(cell encodedPacket, int newNonce) inline method_id { + slice parsingPacket = encodedPacket.begin_parse(); + (parsingPacket, int packetVersion) = BytesDecoder::loadBytes(parsingPacket, 1); + (parsingPacket, int nonce) = BytesDecoder::loadBytes(parsingPacket, 8); + (parsingPacket, int srcEid) = BytesDecoder::loadBytes(parsingPacket, 4); + (parsingPacket, int srcOapp) = BytesDecoder::loadBytes(parsingPacket, 32); + (parsingPacket, int dstEid) = BytesDecoder::loadBytes(parsingPacket, 4); + (parsingPacket, int dstOapp) = BytesDecoder::loadBytes(parsingPacket, 32); + (parsingPacket, int guid) = BytesDecoder::loadBytes(parsingPacket, 32); + + return lz::Packet::New( + lz::Path::New(srcEid, srcOapp, dstEid, dstOapp), + BytesDecoder::compactRemainder(parsingPacket), + newNonce + ); +} + +;; integration helper function returns lz::Packet +cell decodeAndOverrideNonceAndGuid(cell encodedPacket, int newNonce) inline method_id { + slice parsingPacket = encodedPacket.begin_parse(); + (parsingPacket, int packetVersion) = BytesDecoder::loadBytes(parsingPacket, 1); + (parsingPacket, int nonce) = BytesDecoder::loadBytes(parsingPacket, 8); + (parsingPacket, int srcEid) = BytesDecoder::loadBytes(parsingPacket, 4); + (parsingPacket, int srcOapp) = BytesDecoder::loadBytes(parsingPacket, 32); + (parsingPacket, int dstEid) = BytesDecoder::loadBytes(parsingPacket, 4); + (parsingPacket, int dstOapp) = BytesDecoder::loadBytes(parsingPacket, 32); + (parsingPacket, int guid) = BytesDecoder::loadBytes(parsingPacket, 32); + + cell path = lz::Path::New(srcEid, srcOapp, dstEid, dstOapp); + return lz::Packet::New( + path, + BytesDecoder::compactRemainder(parsingPacket), + newNonce + ).cl::set( + lz::Packet::guid, + lz::Packet::calculateGuid(path, newNonce) + ); +} + +;; integration helper function returns lz::Packet +cell decodeAndOverrideNonceAndGuidSerialized(cell encodedPacket, int newNonce) inline method_id { + slice parsingPacket = encodedPacket.begin_parse(); + (parsingPacket, int packetVersion) = BytesDecoder::loadBytes(parsingPacket, 1); + (parsingPacket, int nonce) = BytesDecoder::loadBytes(parsingPacket, 8); + (parsingPacket, int srcEid) = BytesDecoder::loadBytes(parsingPacket, 4); + (parsingPacket, int srcOapp) = BytesDecoder::loadBytes(parsingPacket, 32); + (parsingPacket, int dstEid) = BytesDecoder::loadBytes(parsingPacket, 4); + (parsingPacket, int dstOapp) = BytesDecoder::loadBytes(parsingPacket, 32); + (parsingPacket, int guid) = BytesDecoder::loadBytes(parsingPacket, 32); + + cell path = lz::Path::New(srcEid, srcOapp, dstEid, dstOapp); + return BytesEncoder::build( + lz::Packet::New( + path, + BytesDecoder::compactRemainder(parsingPacket), + newNonce + ).cl::set( + lz::Packet::guid, + lz::Packet::calculateGuid(path, newNonce) + ) + ).BytesEncoder::serialize(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/tests/main.fc new file mode 100644 index 00000000..663e2d97 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/allStorages/tests/main.fc @@ -0,0 +1,13 @@ +#include "../../../../tests/testMain.fc"; + +slice _testName() { return "allStorages"; } + +const int max_lz_message_cells = 100; + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +tuple baseTest::getTests() impure { + return empty_tuple(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/handler.fc new file mode 100644 index 00000000..fdb57ef4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/handler.fc @@ -0,0 +1,542 @@ +#include "../../protocol/core/abstract/protocolHandler.fc"; + +#include "../../funC++/baseInterface.fc"; +#include "../../funC++/classlib.fc"; +#include "../../funC++/actions/dispatch.fc"; + +#include "../../protocol/channel/interface.fc"; +#include "../../protocol/controller/interface.fc"; +#include "../../protocol/endpoint/interface.fc"; +#include "../../protocol/msglibs/interface.fc"; +#include "../../protocol/core/baseStorage.fc"; + +#include "../../classes/msgdata/LzReceiveStatus.fc"; +#include "../../classes/msgdata/LzSend.fc"; +#include "../../classes/msgdata/ExtendedMd.fc"; +#include "../../classes/msgdata/MessagingReceipt.fc"; +#include "../../classes/msgdata/MdAddress.fc"; +#include "../../classes/msgdata/MdObj.fc"; +#include "../../classes/msgdata/Nonce.fc"; +#include "../../classes/msgdata/OptionsExtended.fc"; +#include "../../classes/msgdata/PacketId.fc"; +#include "../../classes/msgdata/SetAddress.fc"; +#include "../../classes/msgdata/SetPeer.fc"; + +#include "../../classes/lz/Config.fc"; +#include "../../classes/lz/Packet.fc"; + +#include "storage.fc"; +#include "interface.fc"; + +;; WARNING: This is a mock OApp, and is not fully functioning, use for testing purposes only. + +;;; ================================= VIRTUAL FUNCTIONS =================================== +;; all need to be implemented by the OApp + +;; -- storage virtual functions -- +;; returns the base oApp storage object from the contract storage +cell getBaseOAppStorage() impure inline method_id; + +;; this doesn't set the actual contract storage, rather returns the new storage object +cell setBaseOAppStorage(cell $storage, cell $newBaseOAppStorage) impure inline; + +;; -- inside virtual functions -- +tuple _lzReceivePrepare(cell $storage, tuple actions, cell $packet) impure inline; +(cell, tuple, int) _lzReceiveExecute(cell $storage, tuple actions, cell $packet) impure inline; +(cell, tuple) _sendCallback(cell $storage, tuple actions, cell $messagingReceipt) impure inline; + +;;; ================================= INTERFACE FUNCTIONS =================================== + +int _getEventSink() impure inline { + return getContractAddress(); +} + +;;; ================================= HELPER FUNCTIONS =================================== + +;; Derive the endpoint address for a given destination EID +int _getEndpointAddress(int dstEid) impure inline method_id { + cell $baseOAppStorage = getBaseOAppStorage(); + return computeContractAddress( + $baseOAppStorage + .cl::get(BaseOApp::endpointInitStorage) + .cl::set(Endpoint::dstEid, dstEid), + $baseOAppStorage.cl::get(BaseOApp::endpointCode) + ); +} + +;; Derive the expected address of the channel given a particular initial storage +int _getChannelAddressFromStorageInit(cell $channelStorageInit) impure inline method_id { + cell $baseOAppStorage = getBaseOAppStorage(); + cell channelCode = $baseOAppStorage.cl::get(BaseOApp::channelCode); + return computeContractAddress($channelStorageInit, channelCode); +} + +;; Derive the channel address for a given path +int _getChannelAddress(cell $path) impure inline method_id { + return _getChannelAddressFromStorageInit( + Channel::New( + getBaseOAppStorage().cl::get
(BaseOApp::controllerAddress), + $path, + _getEndpointAddress( + $path.cl::get(lz::Path::dstEid) + ) + ) + ); +} + +;; Assert the caller of the current transaction is a valid channel +() assertChannelAddress(cell $channelStorageInit) impure inline { + int channelAddress = _getChannelAddressFromStorageInit($channelStorageInit); + throw_unless(ERROR::WrongChannel, channelAddress == getCaller()); +} + +() _assertUnpackedPath(int srcEid, int srcOApp, int dstEid, int dstOApp) impure inline { + cell $baseOAppStorage = getBaseOAppStorage(); + + ;; make sure peer exists + (int peerAddress, int peerExists) = $baseOAppStorage.cl::nestedDict256::get( + BaseOApp::peers, + dstEid + ); + + ;; throw if any part of the path is incorrect + throw_unless(ERROR::WrongSrcEid, srcEid == $baseOAppStorage.cl::get(BaseOApp::eid)); + throw_unless(ERROR::PeerNotSet, peerExists); + throw_unless(ERROR::WrongSrcOApp, srcOApp == getContractAddress()); + throw_unless(ERROR::WrongPeer, peerAddress == dstOApp); +} + +() _assertSendPath(cell $sendPath) impure inline { + int srcEid = $sendPath.cl::get(lz::Path::srcEid); + int srcOApp = $sendPath.cl::get
(lz::Path::srcOApp); + int dstEid = $sendPath.cl::get(lz::Path::dstEid); + int dstOApp = $sendPath.cl::get
(lz::Path::dstOApp); + + _assertUnpackedPath(srcEid, srcOApp, dstEid, dstOApp); +} + +() _assertReceivePath(cell $receivePath) impure inline { + int srcEid = $receivePath.cl::get(lz::Path::dstEid); + int srcOApp = $receivePath.cl::get
(lz::Path::dstOApp); + int dstEid = $receivePath.cl::get(lz::Path::srcEid); + int dstOApp = $receivePath.cl::get
(lz::Path::srcOApp); + + _assertUnpackedPath(srcEid, srcOApp, dstEid, dstOApp); +} + +;; invariant is that enforced is either NULL, or a valid classlib object +() _assertOptionsValid(cell $extraOptions, cell $enforcedOptions) impure inline { + if ( + (~ $extraOptions.cl::isNullObject()) + & (~ $enforcedOptions.cl::isNullObject()) + ) { + ;; extra == 'something', enforced == 'something' + throw_unless(ERROR::InvalidExtraOptionsVersion, $extraOptions.cl::equalObjTypeShallow($enforcedOptions)); + } elseif ($enforcedOptions.cl::isNullObject()) { + ;; extra == 'something', enforced == 'null' + ;; ensure that the enforced options are well formed, and dont contain any arbitrary refs + throw_unless(ERROR::InvalidExtraOptions, $extraOptions.cl::noRefFields()); + } else { + ;; extra == 'null', enforced == 'null' + ;; OR + ;; extra == 'null', enforced == 'something' + + ;; do nothing + } +} + +() _assertGas(int gasRequired) impure inline { } + +;;; ================ PERMISSION FUNCTIONS ===================== + +() _oAppCheckPermissions(int op, cell $md) impure inline { + if ( + (op == Layerzero::OP::LZ_RECEIVE_EXECUTE) + | (op == Layerzero::OP::CHANNEL_SEND_CALLBACK) + | (op == Layerzero::OP::NILIFY_CALLBACK) + | (op == Layerzero::OP::BURN_CALLBACK) + ) { ;; op codeb + assertChannelAddress($md.cl::get(md::MdObj::obj)); + } elseif ( + (op == OP::DeployChannel) + | (op == OP::DeployConnection) + | (op == OP::SetLzConfig) + | (op == OP::SetPeer) + | (op == OP::SetEnforcedOptions) + | (op == OP::Nilify) + | (op == OP::Burn) + ) { + assertOwner(); + } elseif (op == Layerzero::OP::LZ_RECEIVE_PREPARE) { + return (); + } else { + ;; we must put a check for all opcodes to make sure we don't + ;; mistakenly miss an opp code's permissions + throw(BaseInterface::ERROR::invalidOpcode); + } +} + +;;; ========================== HANDLERS ======================================= + +;; ----------- Setter handlers ----------- + +;; Sets a new owner for the OApp +tuple setOwner(cell $setAddressMd) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $baseStorage = + $storage.cl::get(BASE_STORAGE_INDEX) + .cl::set( + BaseStorage::owner, + $setAddressMd.cl::get
(md::SetAddress::address) + ); + + setContractStorage($storage.cl::set(BASE_STORAGE_INDEX, $baseStorage)); + + return actions; +} + +;; Sets the peer address for a given endpoint id +tuple setPeer(cell $setPeer) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $newBaseOAppStorage = getBaseOAppStorage() + .cl::nestedDict256::set( + BaseOApp::peers, + $setPeer.cl::get(md::SetPeer::eid), + $setPeer.cl::get(md::SetPeer::peer) + ); + + setContractStorage($storage.setBaseOAppStorage($newBaseOAppStorage)); + actions~pushAction(EVENT::PeerSet, $setPeer); + + return actions; +} + +;; derive the enforcedOptions mapping key +int getEnforcedOptionsKey(int eid, int msgType) impure inline method_id { + return (eid << 32) | msgType; +} + +cell getEnforcedOptions(int eid, int msgType) impure inline method_id { + (cell $enforcedOptions, int exists) = getBaseOAppStorage() + .cl::get(BaseOApp::enforcedOptions) + .cl::dict256::get(getEnforcedOptionsKey(eid, msgType)); + + if (exists) { + return $enforcedOptions; + } else { + return cl::nullObject(); + } +} + +;; Sets the enforced option for a given msg type and dstEid +tuple setEnforcedOptions(cell $optionsExtended) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $enforcedOptions = $optionsExtended.cl::get(md::OptionsExtended::options); + + ;; ensure that the enforced options are well formed, and dont contain any arbitrary refs + throw_unless(ERROR::InvalidEnforcedOptions, $enforcedOptions.cl::noRefFields()); + + ;; update the enforced options to that key + cell $baseOAppStorage = getBaseOAppStorage() + .cl::nestedDict256::setRef( + BaseOApp::enforcedOptions, + getEnforcedOptionsKey( + $optionsExtended.cl::get(md::OptionsExtended::eid), + $optionsExtended.cl::get(md::OptionsExtended::msgType) + ), + $enforcedOptions + ); + + ;; store the updated mapping + setContractStorage($storage.setBaseOAppStorage($baseOAppStorage)); + + ;; emit event + actions~pushAction(EVENT::EnforcedOptionsSet, $optionsExtended); + + return actions; +} + +;; @in external from owner +;; @in_md Config(path, forwardingAddress, opCode, config) +;; @out [arbitrary lzconfig handler] +;; @out_md ExtendedMd([arbitrary config md], path, nullAddress) +tuple setLzConfig(cell $config) impure inline method_id { + (_, tuple actions) = preamble(); + + ;; we use the send path here becuase the send path is used to derive the channel address + ;; and the channel is used to configure both send and receive configs + cell $sendPath = $config.cl::get(lz::Config::path); + _assertSendPath($sendPath); + + actions~pushAction( + $config.cl::get
(lz::Config::forwardingAddress), + $config.cl::get(lz::Config::opCode), + md::MdObj::New( + $config.cl::get(lz::Config::config), + $sendPath + ) + ); + + return actions; +} + +;; ----------- Lz Send handlers ----------- + +;; in: external from user +;; out: endpoint/handler.fc/send +;; @out_md lzSend +;; This is not an interface function, it should be called by the OApp, not from outside +tuple _lzSend( + int dstEid, + int msgType, + int nativeFee, + int zroFee, + cell $extraOptions, + cell lzMessage, + int callNanos, + cell callbackData, + tuple actions +) impure inline { + cell $baseOAppStorage = getBaseOAppStorage(); + + ;; assert the remote peer is valid + (int peerAddress, int exists) = $baseOAppStorage + .cl::nestedDict256::get(BaseOApp::peers, dstEid); + throw_unless(ERROR::PeerNotSet, exists); + + cell $sendPath = lz::Path::New( + $baseOAppStorage.cl::get(BaseOApp::eid), + getContractAddress(), + dstEid, + peerAddress + ); + + ;; check that the options being used are valid + cell $enforcedOptions = getEnforcedOptions(dstEid, msgType); + _assertOptionsValid($extraOptions, $enforcedOptions); + + cell $lzSendMd = md::LzSend::New( + nativeFee, + zroFee, + $extraOptions, + $enforcedOptions, + lz::Packet::nonceless( + $sendPath, + lzMessage + ), + callbackData + ); + + if (callNanos > 0) { + actions~pushAction( + _getEndpointAddress(dstEid), + Endpoint::OP::ENDPOINT_SEND, + $lzSendMd, + callNanos + ); + } else { + actions~pushAction( + _getEndpointAddress(dstEid), + Endpoint::OP::ENDPOINT_SEND, + $lzSendMd + ); + } + + return actions; +} + +;; in: Channel::send_callback (currently msglib_quote_callback) +;; @in_md MdObj( messagingReceipt(lzSend, quoted_nativeFee, quoted_zroFee, errorCode), initialChannelStorage) +;; out: oApp (refund excess funds) +tuple channelSendCallback(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $messagingReceipt = $mdObj.cl::get(md::MdObj::md); + ($storage, actions) = _sendCallback($storage, actions, $messagingReceipt); + + setContractStorage($storage); + return actions; +} + +;; --------------- Lz Receive handlers --------------- + +;;; ========================================== +;; lzReceive follows a two-phase commit pattern in TON +;; first, a permissionless call is made to lzReceivePrepare +;; failure => permissionless retry at endpoint +;; second, the OApp performs a gas assertion and sends a clear request to the Packet +;; failure => permissionless retry from step 1 at endpoint +;; third, the Packet locks itself and notifies the OApp to execute the request +;; failure => OApp must unlock the Packet and retry from step 1 +;; finally, the OApp clears and destroys the message +;; failure => can be ignored, but the OApp will be blocklisted by the executor +;;; ========================================== + +;; hot path: lzReceivePrepare must do a gas assertion +;; @in_opcode Layerzero::OP::LZ_RECEIVE_PREPARE +;; @in_from channel/handler.fc/lzReceivePrepare +;; @in_md Packet +;; @out_opcode Channel::OP::LZ_RECEIVE_LOCK +;; @out_to channel +;; @out_md Packet +;; @permissions: permissionless +tuple lzReceivePrepare(cell $packet) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + ;; assert the packet is coming from a valid path + _assertReceivePath($packet.cl::get(lz::Packet::path)); + + actions = _lzReceivePrepare($storage, actions, $packet); + + ;; send lock request to channel + actions~pushAction( + getCaller(), + Channel::OP::LZ_RECEIVE_LOCK, + md::Nonce::New($packet.cl::get(lz::Packet::nonce)) + ); + + return actions; +} + +;; In OApps like OFT, this function will be a NOP +;; @in_opcode Layerzero::OP::LZ_RECEIVE_EXECUTE +;; @in_from channel/handler.fc/lzReceiveLock +;; @in_md mdExtended(Packet, init_state_{Channel}) +;; @out_opcode Channel::OP::CLEAR +;; @out_to channel +;; @out_md success_or_fail +;; @permissions: permissionless +tuple lzReceiveExecute(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $packet = $mdObj.cl::get(md::MdObj::md); + ($storage, actions, int successOrFail) = _lzReceiveExecute($storage, actions, $packet); + + setContractStorage($storage); + actions~pushAction( + getCaller(), + Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK, + md::LzReceiveStatus::New( + successOrFail, + $packet.cl::get(lz::Packet::nonce) + ) + ); + + return actions; +} + +;; --------------- Management handlers --------------- + +;; Calls on the controller to deploy a channel for this OApp +tuple deployChannel(cell $deploy) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + int controllerAddress = getBaseOAppStorage().cl::get
(BaseOApp::controllerAddress); + + setContractStorage($storage); + actions~pushAction( + controllerAddress, + Controller::OP::DEPLOY_CHANNEL, + $deploy + ); + + return actions; +} + +;; Calls on the controller to deploy a channel for this OApp +;; in md::MdAddress::New w/ deployChannel +tuple deployConnection(cell $mdAddress) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $deploy = $mdAddress.cl::get(md::MdAddress::md); + + setContractStorage($storage); + actions~pushAction( + $mdAddress.cl::get
(md::MdAddress::address), + MsglibManager::OP::DEPLOY_CONNECTION, + $deploy + ); + + return actions; +} + +;; Attempt to abort a Packet +;; @in: owner +;; @in_opcode OP::FORCE_ABORT +;; @in_md $lzSend +;; @out_opcode Channel::OP::FORCE_ABORT +;; @out_to channel +;; @out_md $lzSend +;; @permissions: only owner +tuple forceAbort(cell $lzSend) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sendPath = $lzSend + .cl::get(md::LzSend::packet) + .cl::get(lz::Packet::path); + + _assertSendPath($sendPath); + + setContractStorage($storage); + actions~pushAction( + _getChannelAddress($sendPath), + Channel::OP::FORCE_ABORT, + $lzSend + ); + + return actions; +} + +;; Burns nonce on channel +;; @in_opcode OP::BURN +;; @in_md packetId +;; @out_opcode Channel::OP::BURN +;; @out_to channel +;; @out_md packetId +;; @permissions: only owner +;; @permissions: permissionless +tuple burn(cell $packetId) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + ;; ============== Assertions ==================== + cell $receivePath = $packetId.cl::get(md::PacketId::path); + + _assertReceivePath($receivePath); + + ;; ============== Base OApp actions ============== + actions~pushAction( + _getChannelAddress($receivePath.lz::Path::reverse()), + Channel::OP::BURN, + $packetId + ); + + return actions; +} + +;; Marks a Packet as 'uncommitted' until it is re-committed +;; @in_opcode OP::NILIFY +;; @in_md packetId +;; @out_opcode Channel::OP::NILIFY +;; @out_to channel +;; @out_md packetId +;; @permissions: only owner +;; @permissions: permissionless (only owner) +tuple nilify(cell $packetId) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $receivePath = $packetId.cl::get(md::PacketId::path); + _assertReceivePath($receivePath); + + ;; send the nilify message to the channel address + actions~pushAction( + ;; the correct channel corresponds to this "send" path + _getChannelAddress($receivePath.lz::Path::reverse()), + Channel::OP::NILIFY, + $packetId + ); + + return actions; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/interface.fc new file mode 100644 index 00000000..56be1573 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/interface.fc @@ -0,0 +1,38 @@ +#include "./storage.fc"; + +const int OP::SetOwner = "OP::SetOwner"c; + +const int OP::DeployChannel = "OP::DeployChannel"c; +const int OP::DeployConnection = "OP::DeployConnection"c; +const int OP::ForceAbort = "OP::ForceAbort"c; +const int OP::Burn = "OP::Burn"c; +const int OP::Nilify = "OP::Nilify"c; +const int OP::SetPeer = "OP::SetPeer"c; +const int OP::SetEnforcedOptions = "OP::SetEnforcedOptions"c; +const int OP::SetLzConfig = "OP::SetLzConfig"c; +const int OP::TransferOwnership = "OP::TransferOwnership"c; +const int OP::ClaimOwnership = "OP::ClaimOwnership"c; + +;; event topics +const int EVENT::PeerSet = "EVENT::PeerSet"u; +const int EVENT::OwnerSet = "EVENT::OwnerSet"u; +const int EVENT::TentativeOwnerSet = "EVENT::TentativeOwnerSet"u; +const int EVENT::EnforcedOptionsSet = "EVENT::EnforcedOptionsSet"u; +const int EVENT::LzReceiveExecuteFailed = "EVENT::LzReceiveExecuteFailed"u; + +;; error codes +const int ERROR::PeerNotSet = "ERROR::PeerNotSet"c & ERRORCODE_MASK; +const int ERROR::WrongPeer = "ERROR::WrongPeer"c & ERRORCODE_MASK; +const int ERROR::WrongSrcEid = "ERROR::WrongSrcEid"c & ERRORCODE_MASK; +const int ERROR::WrongSrcOApp = "ERROR::WrongSrcOApp"c & ERRORCODE_MASK; +const int ERROR::WrongChannel = "ERROR::WrongChannel"c & ERRORCODE_MASK; +const int ERROR::WrongChannelOwner = "ERROR::WrongChannelOwner"c & ERRORCODE_MASK; +const int ERROR::InsufficientGas = "ERROR::InsufficientGas"c & ERRORCODE_MASK; +const int ERROR::InvalidExtraOptions = "ERROR::InvalidExtraOptions"c & ERRORCODE_MASK; +const int ERROR::InvalidEnforcedOptions = "ERROR::InvalidEnforcedOptions"c & ERRORCODE_MASK; +const int ERROR::InvalidExtraOptionsVersion = "ERROR::InvalidExtraOptionsVersion"c & ERRORCODE_MASK; +const int ERROR::NullTentativeOwner = "ERROR::NullTentativeOwner"c & ERRORCODE_MASK; +const int ERROR::OnlyTentativeOwner = "ERROR::OnlyTentativeOwner"c & ERRORCODE_MASK; +const int ERROR::OnlyOwner = "ERROR::OnlyOwner"c & ERRORCODE_MASK; +const int ERROR::NotAuthenticated = "ERROR::NotAuthenticated"c & ERRORCODE_MASK; +const int ERROR::NotInitialized = "ERROR::NotInitialized"c & ERRORCODE_MASK; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/oAppMain.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/oAppMain.fc new file mode 100644 index 00000000..910161ab --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/oAppMain.fc @@ -0,0 +1,52 @@ +#include "handler.fc"; +#include "interface.fc"; +#include "../../funC++/handlerCore.fc"; +#include "../../funC++/stdlib.fc"; + +;; WARNING: This is a mock OApp, and is not fully functioning, use for testing purposes only. + +tuple runOAppHandler(int op, cell $md) impure inline { + ;; ----------- Setter handlers ----------- + if (op == OP::SetOwner) { + return setOwner($md); + } elseif (op == OP::SetPeer) { + return setPeer($md); + } + ;; ----------- Lz Send handlers ----------- + elseif (op == Layerzero::OP::CHANNEL_SEND_CALLBACK) { + return channelSendCallback($md); + } + ;; ----------- Lz Receive handlers -------- + elseif (op == Layerzero::OP::LZ_RECEIVE_EXECUTE) { + return lzReceiveExecute($md); + } elseif (op == Layerzero::OP::LZ_RECEIVE_PREPARE) { + return lzReceivePrepare($md); + } + ;; ----------- Management handlers --------- + elseif (op == OP::Burn) { + return burn($md); + } elseif (op == Layerzero::OP::BURN_CALLBACK) { + return emptyActions(); ;; burn callback is not implemented by default + } elseif (op == OP::DeployChannel) { + return deployChannel($md); + } elseif (op == OP::DeployConnection) { + return deployConnection($md); + } elseif (op == OP::ForceAbort) { + return forceAbort($md); + } elseif (op == OP::Nilify) { + return nilify($md); + } elseif (op == Layerzero::OP::NILIFY_CALLBACK) { + return emptyActions(); ;; nilify callback is not implemented by default + } + ;; ---------- Set config handlers---------- + elseif (op == OP::SetLzConfig) { + return setLzConfig($md); + } elseif (op == OP::SetEnforcedOptions) { + return setEnforcedOptions($md); + } + ;; ---------- Opcode not found ------------- + else { + throw(BaseInterface::ERROR::invalidOpcode); + } + return empty_tuple(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/storage.fc new file mode 100644 index 00000000..fad186e5 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/baseOApp/storage.fc @@ -0,0 +1,46 @@ +#include "../../protocol/core/baseStorage.fc"; +#include "../../funC++/constants.fc"; +#include "../../classes/lz/Path.fc"; +#include "../../protocol/channel/storage.fc"; +#include "../../protocol/endpoint/storage.fc"; + +;; WARNING: This is a mock OApp, and is not fully functioning, use for testing purposes only. + +const int BASE_LZ_RECEIVE_GAS = 100000; +;; required object name +const int BaseOApp::NAME = "baseOApp"u; + +;; field names +const int BaseOApp::controllerAddress = 0; +const int BaseOApp::eid = 1; +const int BaseOApp::maxReceivedNonce = 2; +const int BaseOApp::baseLzReceiveGas = 3; +const int BaseOApp::peers = 4; +const int BaseOApp::enforcedOptions = 5; +const int BaseOApp::tentativeOwner = 6; +const int BaseOApp::endpointCode = 7; +const int BaseOApp::channelCode = 8; +const int BaseOApp::endpointInitStorage = 9; + +;; @owner oApp owner EOA +cell BaseOApp::New(int controllerAddress, int eid, cell endpointCode, cell channelCode) inline method_id { + return cl::declare( + BaseOApp::NAME, + unsafeTuple([ + [cl::t::address, controllerAddress], ;; BaseOApp::controllerAddress + [cl::t::uint32, eid], ;; BaseOApp::eid + [cl::t::dict256, cl::dict256::New()], ;; BaseOApp::maxReceivedNonce + [cl::t::coins, BASE_LZ_RECEIVE_GAS], ;; BaseOApp::baseLzReceiveGas + [cl::t::dict256, cl::dict256::New()], ;; BaseOApp::peers + [cl::t::dict256, cl::dict256::New()], ;; BaseOApp::enforcedOptions + [cl::t::address, NULLADDRESS], ;; BaseOApp::tentativeOwner + [cl::t::cellRef, endpointCode], ;; BaseOApp::endpointCode + [cl::t::cellRef, channelCode], ;; BaseOApp::channelCode + [cl::t::objRef, Endpoint::New( + eid, + 0, + controllerAddress + )] ;; BaseOApp::endpointInitStorage + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/autoload.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/autoload.fc new file mode 100644 index 00000000..d3727d2b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/autoload.fc @@ -0,0 +1,3 @@ +#include "handler.fc"; +#include "interface.fc"; +#include "storage.fc"; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/handler.fc new file mode 100644 index 00000000..d0a1d6a7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/handler.fc @@ -0,0 +1,219 @@ +;; https://github.com/LayerZero-Labs/monorepo/blob/main/packages/layerzero-v2/evm/oapp/contracts/oapp/examples/OmniCounterAbstract.sol +#include "../../classes/lz/Packet.fc"; +#include "../../classes/lz/Path.fc"; +#include "../../classes/msgdata/CounterIncrement.fc"; +#include "../../classes/msgdata/ExtendedMd.fc"; +#include "../../classes/msgdata/Nonce.fc"; +#include "../../classes/msgdata/OptionsV1.fc"; +#include "../../classes/msgdata/PacketId.fc"; +#include "../../classes/msgdata/SetAddress.fc"; +#include "../../classes/msgdata/SetPeer.fc"; +#include "../../funC++/handlerCore.fc"; +#include "../../funC++/baseInterface.fc"; +#include "../../protocol/channel/interface.fc"; +#include "../../protocol/interfaces.fc"; +#include "../../protocol/msglibs/BytesEncoder.fc"; +#include "../../classes/msgdata/LzSend.fc"; + +#include "interface.fc"; +#include "storage.fc"; + +#include "../baseOApp/handler.fc"; +#include "../../funC++/classlib.fc"; +#include "../../protocol/core/abstract/protocolHandler.fc"; +#include "../../protocol/msglibs/BytesDecoder.fc"; + +;;; ================STORAGE FUNCTIONS===================== +cell getBaseOAppStorage() impure method_id { + return getContractStorage().cl::get(Counter::baseOAppStorage); +} + +cell setBaseOAppStorage(cell $storage, cell $newBaseOAppStorage) { + return $storage.cl::set(Counter::baseOAppStorage, $newBaseOAppStorage); +} + +;;; ================INTERFACE FUNCTIONS===================== + +(cell, tuple) _initialize(cell $md) impure inline { + return preamble(); +} + +int lzReceiveGas(cell $packet) impure inline method_id { + return 10 * 1000; +} + +int lzSendGas(int opcode, cell $input) impure method_id { + return 10 * 1000; +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + ;; Check for custom ops first, then *must* call the baseOAppCheckPermissions + if ( + (op == Counter::OP::INCREMENT) + | (op == Counter::OP::FAIL_NEXT_LZ_RECEIVE) + | (op == OP::ForceAbort) + ) { + return (); + } else { + return _oAppCheckPermissions(op, $md); + } +} + +;;; ==========================HANDLERS===================================== + +;;; =============== Send flow handlers ============== + +;; counter increment +;; in: external from user +;; out: endpoint/handler.fc/send +;; @out_md lzSend +tuple increment(cell $md) impure inline { + (cell $storage, tuple actions) = preamble(); + + int incrementType = $md.cl::get(md::CounterIncrement::incrementType); + int srcEid = getBaseOAppStorage().cl::get(BaseOApp::eid); + + tuple message = BytesEncoder::New() + .BytesEncoder::feed(incrementType) + .BytesEncoder::feed(srcEid); + if (incrementType == Counter::increment::ab) { + cell extraOptions = $md.cl::get(md::CounterIncrement::extraOptions); + ifnot (extraOptions.cell_is_empty()) { + int receiveValue = extraOptions.cl::get(md::OptionsV1::lzReceiveValue); + message = message.BytesEncoder::feed(receiveValue); + } + } elseif (incrementType == Counter::increment::aba) { + message = message + .BytesEncoder::feed($md.cl::get(md::CounterIncrement::nativeFee)) + .BytesEncoder::feed($md.cl::get(md::CounterIncrement::zroFee)) + ; + } else { + throw(Counter::ERROR::invalidIncrementType); + } + + int dstEid = $md.cl::get(md::CounterIncrement::dstEid); + + actions = _lzSend( + dstEid, + incrementType, + $md.cl::get(md::CounterIncrement::nativeFee), + $md.cl::get(md::CounterIncrement::zroFee), + $md.cl::get(md::CounterIncrement::extraOptions), + message.BytesEncoder::serialize(), + 0, + empty_cell(), + actions + ); + + ;; pull the counter dict + (int existingCount, int found) = $storage + .cl::nestedDict256::get(Counter::outboundCount, dstEid); + int count = (~ found) ? 1 : existingCount + 1; + + ;; store the state + setContractStorage( + $storage.cl::nestedDict256::set(Counter::outboundCount, dstEid, count) + ); + + return actions; +} + +;;; ============== Custom inside handlers ==================== + +(cell, tuple) _sendCallback(cell $storage, tuple actions, cell $messagingReceipt) impure inline { + int errorCode = $messagingReceipt.cl::get(md::MessagingReceipt::errorCode); + if (errorCode != Channel::NO_ERROR) { + cell $lzSend = $messagingReceipt.cl::get(md::MessagingReceipt::lzSend); + cell $packet = $lzSend.cl::get(md::LzSend::packet); + int remoteEid = $packet + .cl::get(lz::Packet::path) + .cl::get(lz::Path::dstEid); + (int existingCount, int found) = $storage + .cl::nestedDict256::get(Counter::outboundCount, remoteEid); + if (found) { + $storage = $storage + .cl::nestedDict256::set( + Counter::outboundCount, + remoteEid, + existingCount - 1 + ); + } + } + + return ($storage, actions); +} + +int _lzReceiveGas(cell $packet) impure inline method_id { + return 100; ;; this should the amount of gas required to execute the packet +} + +tuple _lzReceivePrepare(cell $storage, tuple actions, cell $packet) impure inline method_id { + return actions; +} + +(cell, tuple, int) _lzReceiveExecute(cell $storage, tuple actions, cell $packet) impure inline { + int remoteEid = $packet + .cl::get(lz::Packet::path) + .cl::get(lz::Path::srcEid); + + slice message_s = $packet.cl::get(lz::Packet::message).begin_parse(); + + int messageType = message_s~load_uint8(); + int successOrFail = true; + if ($storage.cl::get(Counter::failNextLzReceive)) { + $storage = $storage.cl::set(Counter::failNextLzReceive, false); + successOrFail = false; + return ($storage, actions, successOrFail); + } elseif (messageType == Counter::increment::ab) { + ;; do nothing + } elseif (messageType == Counter::increment::aba) { + var [balance, _] = get_balance(); + (message_s, int nativeFee) = BytesDecoder::loadBytes(message_s, 16); + (message_s, int zroFee) = BytesDecoder::loadBytes(message_s, 16); + actions = _lzSend( + remoteEid, + Counter::increment::ab, ;; get enforced options for B->A, which is a ab incrementat this point + nativeFee, + ;; 100, + zroFee, + cl::nullObject(), ;; todo: non-empty options for ABA + begin_cell().store_uint8(Counter::increment::ab).end_cell(), + ;; todo: this should be passed in via options and not hardcoded + 250000000, ;; send with 0.25 ton + empty_cell(), + actions + ); + ;; pull the counter dict + (int existingCount, int found) = $storage + .cl::nestedDict256::get(Counter::outboundCount, remoteEid); + int count = (~ found) ? 1 : existingCount + 1; + + ;; store the state + $storage = $storage + .cl::nestedDict256::set( + Counter::outboundCount, + remoteEid, + count + ); + } + + ;; pull the counter dict + (int existingCount, int found) = $storage + .cl::nestedDict256::get(Counter::inboundCount, remoteEid); + int count = (~ found) ? 1 : existingCount + 1; + + return ( + $storage + .cl::nestedDict256::set(Counter::inboundCount, remoteEid, count), + actions, + successOrFail + ); +} + +tuple failNextLzReceive() impure inline method_id { + (cell $storage, tuple actions) = preamble(); + setContractStorage($storage.cl::set(Counter::failNextLzReceive, true)); + return actions; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/interface.fc new file mode 100644 index 00000000..503f6648 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/interface.fc @@ -0,0 +1,12 @@ +#include "../baseOApp/interface.fc"; +#include "storage.fc"; + +;; Operation codes +const int Counter::OP::INCREMENT = "Counter::OP::INCREMENT"c; +const int Counter::OP::FAIL_NEXT_LZ_RECEIVE = "Counter::OP::FAIL_NEXT_LZ_RECEIVE"c; + +;; Increment modes +const int Counter::increment::ab = 1; +const int Counter::increment::aba = 2; + +const int Counter::ERROR::invalidIncrementType = "invalidIncrementType"c & ERRORCODE_MASK; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/main.fc new file mode 100644 index 00000000..ac8c17d8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/main.fc @@ -0,0 +1,37 @@ +;;; ================================================================ +;; The base main function for LayerZero Endpoint, UltraLightNode, and OApp +;;; ================================================================ +#include "../../funC++/contractMain.fc"; +#include "../baseOApp/oAppMain.fc"; +#include "../../protocol/core/abstract/protocolHandler.fc"; +#include "../../funC++/actions/call.fc"; +#include "../../funC++/actions/event.fc"; +#include "../../funC++/actions/dispatch.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == Counter::OP::INCREMENT) { + return increment($md); + } elseif (op == Counter::OP::FAIL_NEXT_LZ_RECEIVE) { + return failNextLzReceive(); + } else { + return runOAppHandler(op, $md); + } +} + +int _executeAction(int actionType, tuple action) impure inline { + if (actionType == action::event::NAME) { + return executeEvent(action); + } elseif (actionType == action::call::NAME) { + return executeCall(action); + } elseif (actionType == action::dispatch::NAME) { + return executeDispatch(action); + } else { + throw(BaseInterface::ERROR::invalidActionType); + } + + ;; compiler freaks out if you dont have something here returning an int, but this should never be reached + return false; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/storage.fc new file mode 100644 index 00000000..647f9683 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/storage.fc @@ -0,0 +1,33 @@ +#include "../../protocol/core/baseStorage.fc"; +#include "../baseOApp/storage.fc"; + +;; required object name +const int Counter::NAME = "counter"u; + +;; field names +const int Counter::baseStorage = 0; +const int Counter::baseOAppStorage = 1; +const int Counter::inboundCount = 2; +const int Counter::outboundCount = 3; +const int Counter::failNextLzReceive = 4; +const int Counter::id = 5; + +;; @owner oApp owner EOA +cell Counter::New(int owner, int controllerAddress, int eid, int id, cell endpointCode, cell channelCode) inline method_id { + return cl::declare( + Counter::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; Counter::baseStorage + [cl::t::objRef, BaseOApp::New( + controllerAddress, + eid, + endpointCode, + channelCode + )], ;; Counter::baseOAppStorage + [cl::t::dict256, cl::dict256::New()], ;; Counter::inboundCount + [cl::t::dict256, cl::dict256::New()], ;; Counter::outboundCount + [cl::t::bool, false], ;; Counter::failNextLzReceive + [cl::t::uint32, id] ;; Counter::id + ]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/main.fc new file mode 100644 index 00000000..21a4ee4e --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/main.fc @@ -0,0 +1,609 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/PacketId.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../protocol/channel/interface.fc"; +#include "../../baseOApp/handler.fc"; +#include "../../baseOApp/interface.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/msgdata/MdObj.fc"; +#include "../../../classes/msgdata/OptionsExtended.fc"; +#include "../../../classes/msgdata/OptionsV2.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "counter"; } + +cell createContractStorage() impure { + setContractStorage( + Counter::New( + getCaller(), + CONTROLLER_ADDRESS, + SRC_EID, + COUNTER_ID, + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE() + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================HELPER FUNCTIONS=============================== + +const int COUNTER_NONCE = 1; + +cell abIncrementPacket() impure { + return begin_cell().store_uint8(Counter::increment::ab) + .store_uint32(SRC_EID) + .store_uint32(2) + .end_cell(); +} + +cell _getCounterSendPath() impure { + return lz::Path::New( + SRC_EID, + getContractAddress(), + DST_EID, + DST_OAPP + ); +} + +cell _getCounterReceivePath() impure { + return lz::Path::New( + DST_EID, + DST_OAPP, + SRC_EID, + getContractAddress() + ); +} + +cell _setAndGetPeer(cell $storage, int dstEid, int dstOApp) { + return $storage.setBaseOAppStorage( + getBaseOAppStorage().cl::nestedDict256::set( + BaseOApp::peers, + dstEid, + dstOApp + ) + ); +} + +;;; ===============================TESTS========================================= + +(int, slice) lzReceivePrepare::success::basic(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + cell $packet = lz::Packet::nonceless( + _getCounterReceivePath(), + abIncrementPacket() + ); + + return test::handler::shouldPass( + lzReceivePrepare, + $packet, + unsafeTuple([ + 0, + _newAction( + getCaller(), + Channel::OP::LZ_RECEIVE_LOCK, + md::Nonce::New($packet.cl::get(lz::Packet::nonce)) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) lzReceivePrepare::revert::notEnoughGas(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) lzReceiveExecute::success::ab(cell $storage) impure { + cell $packet = lz::Packet::nonceless( + _getCounterSendPath(), + abIncrementPacket() + ); + + cell $mdExtended = md::ExtendedMd::New( + $packet, + getInitialStorage(), + NULLADDRESS + ); + + ;; determine the expected storage + cell $expectedStorage = $storage.cl::nestedDict256::set( + Counter::inboundCount, + ;; the remoteEid in lzReceive is the path's srcEid + SRC_EID, + 1 + ); + + return test::handler::shouldPass( + lzReceiveExecute, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + getCaller(), + Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK, + md::LzReceiveStatus::New( + true, + $packet.cl::get(lz::Packet::nonce) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) lzReceiveExecute::success::abExistingCount(cell $storage) impure { + ;; arbitrarily set to non zero + int inboundCount = 9; + + ;; store the inbound count + $storage = $storage.cl::nestedDict256::set( + Counter::inboundCount, + ;; the remoteEid in lzReceive is the path's srcEid + SRC_EID, + inboundCount + ); + setContractStorage($storage); + + cell $packet = lz::Packet::nonceless( + _getCounterSendPath(), + abIncrementPacket() + ); + + cell $mdExtended = md::ExtendedMd::New( + $packet, + getInitialStorage(), + NULLADDRESS + ); + + cell $expectedStorage = $storage.cl::nestedDict256::set( + Counter::inboundCount, + ;; the remoteEid in lzReceive is the path's srcEid + SRC_EID, + (inboundCount + 1) + ); + + return test::handler::shouldPass( + lzReceiveExecute, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + getCaller(), + Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK, + md::LzReceiveStatus::New( + true, + $packet.cl::get(lz::Packet::nonce) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) lzReceiveExecute::success::aba(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) lzReceiveExecute::success::abaExistingCount(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) increment::revert::peerNotSet(cell $storage) impure { + return test::handler::shouldFail( + increment, + md::CounterIncrement::New( + DST_EID, + Counter::increment::ab, + MOCK_EXTRA_OPTIONS_V1(), + NATIVE_FEE, + ZRO_FEE + ), + ERROR::PeerNotSet + ); +} + +(int, slice) increment::success::basic(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + ;; Set some enforced options + setEnforcedOptions(MOCK_OPTIONS_EXTENDED()); + + cell $emptyPacket = lz::Packet::nonceless( + _getCounterSendPath(), + abIncrementPacket() + ); + + cell $expectedLzSendMd = md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + $emptyPacket, + empty_cell() + ); + + int endpointAddress = _getEndpointAddress(DST_EID); + + return test::handler::shouldPass( + increment, + md::CounterIncrement::New( + DST_EID, + Counter::increment::ab, + MOCK_EXTRA_OPTIONS_V1(), + NATIVE_FEE, + ZRO_FEE + ), + unsafeTuple([ + 0, + _newAction( + endpointAddress, + Endpoint::OP::ENDPOINT_SEND, + $expectedLzSendMd + ) + ]), + getContractStorage() + .cl::nestedDict256::set(Counter::outboundCount, DST_EID, 1), + txnContext + ); +} + +(int, slice) increment::revert::invalidExtraOptions(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + ;; create an invalid extraOptions + cell $invalidExtraOptions = MOCK_OPTIONS_EXTENDED().cl::set( + md::OptionsExtended::options, + MOCK_OPTIONS_EXTENDED() + ); + + return test::handler::shouldFail( + increment, + md::CounterIncrement::New( + DST_EID, + Counter::increment::ab, + $invalidExtraOptions, + NATIVE_FEE, + ZRO_FEE + ), + ERROR::InvalidExtraOptions + ); +} + +(int, slice) increment::revert::differentOptionTypesV1(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + ;; set some enforced options v2 + cell $enforcedOptionsExtended = md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_EXTRA_OPTIONS_V2() + ); + setEnforcedOptions($enforcedOptionsExtended); + + + ;; extra options v1 + cell $extraOptions = md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_EXTRA_OPTIONS_V1() + ); + + return test::handler::shouldFail( + increment, + md::CounterIncrement::New( + DST_EID, + Counter::increment::ab, + $extraOptions, + NATIVE_FEE, + ZRO_FEE + ), + ERROR::InvalidExtraOptionsVersion + ); +} + +(int, slice) increment::revert::differentOptionTypesV2(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + ;; set some enforced options v1 + cell $enforcedOptionsExtended = md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_EXTRA_OPTIONS_V1() + ); + setEnforcedOptions($enforcedOptionsExtended); + + + ;; extra options v2 + cell $extraOptions = md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_EXTRA_OPTIONS_V2() + ); + + return test::handler::shouldFail( + increment, + md::CounterIncrement::New( + DST_EID, + Counter::increment::ab, + $extraOptions, + NATIVE_FEE, + ZRO_FEE + ), + ERROR::InvalidExtraOptionsVersion + ); +} + +(int, slice) burn::success::basic(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + cell $path = _getCounterReceivePath(); + cell $packetId = md::PacketId::New($path, NONCE); + int channelAddress = _getChannelAddress($path); + + return test::handler::shouldPass( + burn, + $packetId, + unsafeTuple([ + 0, + _newAction( + channelAddress, + Channel::OP::BURN, + md::Nonce::New(NONCE) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) burn::revert::invalidPath(cell $storage) impure { + ;; disable the path to trigger a invalidPath + ;; setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + cell $path = _getCounterReceivePath(); + cell $packetId = md::PacketId::New($path, NONCE); + + return test::handler::shouldFail( + burn, + $packetId, + ERROR::PeerNotSet + ); +} + +(int, slice) nilify::success::basic(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + cell $path = _getCounterReceivePath(); + cell $packetId = md::PacketId::New($path, NONCE); + + return test::handler::shouldPass( + nilify, + $packetId, + unsafeTuple([ + 0, + _newAction( + _getChannelAddress($path), + Channel::OP::NILIFY, + $packetId + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) nilify::revert::invalidPath(cell $storage) impure { + ;; disable the path to trigger a invalidPath + ;; setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + cell $path = _getCounterReceivePath(); + cell $packetId = md::PacketId::New($path, NONCE); + + return test::handler::shouldFail( + nilify, + $packetId, + ERROR::PeerNotSet + ); +} + +(int, slice) forceAbort::success::basic(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + cell $path = _getCounterSendPath(); + cell $packetId = md::PacketId::New($path, NONCE); + cell $lzSendMd = md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + $packetId, + empty_cell() + ); + + return test::handler::shouldPass( + forceAbort, + $lzSendMd, + unsafeTuple([ + 0, + _newAction( + _getChannelAddress($path), + Channel::OP::FORCE_ABORT, + $lzSendMd + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) forceAbort::revert::invalidPath(cell $storage) impure { + ;; disable the path to trigger a invalidPath + ;; setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + cell $path = _getCounterSendPath(); + cell $packetId = md::PacketId::New($path, NONCE); + cell $lzSendMd = md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + $packetId, + empty_cell() + ); + + return test::handler::shouldFail( + forceAbort, + $lzSendMd, + ERROR::PeerNotSet + ); +} + +;; tested via setLzConfig +(int, slice) verifyPath::revert::peerNotSet(cell $storage) impure { + ;; Do not set the peer, this is commented out on purpose + ;; setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + ;; This can arbitrarily set configs, we are just picking rnadom potential address ie. 'Controller' + cell $path = _getCounterSendPath(); + cell $epConfig = MOCK_EP_CONFIG(true); + cell $configMd = lz::Config::New($path, CONTROLLER_ADDRESS, OP::RANDOM, $epConfig); + + ;; We only need to trigger a single error from in here to validate that this hooks into the verifyPath + return test::handler::shouldFail( + setLzConfig, + $configMd, + ERROR::PeerNotSet + ); +} + +;; tested via setLzConfig +(int, slice) verifyPath::revert::wrongSrcOApp(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + ;; This can arbitrarily set configs, we are just picking rnadom potential address ie. 'Controller' + cell $path = _getCounterSendPath(); + ;; set an invalid srcOApp in the path + $path = $path.cl::set(lz::Path::srcOApp, ATTACKER_ADDRESS); + + cell $epConfig = MOCK_EP_CONFIG(true); + cell $configMd = lz::Config::New($path, CONTROLLER_ADDRESS, OP::RANDOM, $epConfig); + + ;; We only need to trigger a single error from in here to validate that this hooks into the verifyPath + return test::handler::shouldFail( + setLzConfig, + $configMd, + ERROR::WrongSrcOApp + ); +} + +;; tested via setLzConfig +(int, slice) verifyPath::revert::wrongPeer(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + ;; This can arbitrarily set configs, we are just picking rnadom potential address ie. 'Controller' + cell $path = _getCounterSendPath(); + ;; set an invalid srcOApp in the path + $path = $path.cl::set(lz::Path::dstOApp, ATTACKER_ADDRESS); + + cell $epConfig = MOCK_EP_CONFIG(true); + cell $configMd = lz::Config::New($path, CONTROLLER_ADDRESS, OP::RANDOM, $epConfig); + + ;; We only need to trigger a single error from in here to validate that this hooks into the verifyPath + return test::handler::shouldFail( + setLzConfig, + $configMd, + ERROR::WrongPeer + ); +} + +(int, slice) channelSendCallback::success::noError(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +;; Doesnt really revert, but it does revert some state +(int, slice) channelSendCallback::revert::error(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) deployChannel::success::basic(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) deployConnection::success::basic(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) lzSend::success::noCallNanos(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) lzSend::success::callNanos(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) lzSend::revert::noPeer(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) lzSend::revert::invalidExtraOptions(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([lzReceivePrepare::success::basic, "lzReceivePrepare::success::basic"]) + .tpush([lzReceivePrepare::revert::notEnoughGas, "lzReceivePrepare::revert::notEnoughGas"]) + .tpush([lzReceiveExecute::success::ab, "lzReceiveExecute::success::ab"]) + .tpush([lzReceiveExecute::success::abExistingCount, "lzReceiveExecuteExistingCount::success::abExistingCount"]) + .tpush([lzReceiveExecute::success::aba, "lzReceiveExecute::success::aba"]) + .tpush([lzReceiveExecute::success::abaExistingCount, "lzReceiveExecute::success::abaExistingCount"]) + .tpush([increment::revert::peerNotSet, "increment::revert::peerNotSet"]) + .tpush([nilify::success::basic, "nilify::success::basic"]) + .tpush([nilify::revert::invalidPath, "nilify::revert::invalidPath"]) + .tpush([forceAbort::success::basic, "forceAbort::success::basic"]) + .tpush([forceAbort::revert::invalidPath, "forceAbort::revert::invalidPath"]) + .tpush([increment::success::basic, "increment::success::basic"]) + .tpush([increment::revert::invalidExtraOptions, "increment::revert::invalidExtraOptions"]) + .tpush([increment::revert::differentOptionTypesV1, "increment::revert::differentOptionTypesV1"]) + .tpush([increment::revert::differentOptionTypesV2, "increment::revert::differentOptionTypesV2"]) + .tpush([burn::success::basic, "burn::success::basic"]) + .tpush([burn::revert::invalidPath, "burn::revert::invalidPath"]) + .tpush([verifyPath::revert::peerNotSet, "verifyPath::revert::peerNotSet"]) + .tpush([verifyPath::revert::wrongSrcOApp, "verifyPath::revert::wrongSrcOApp"]) + .tpush([verifyPath::revert::wrongPeer, "verifyPath::revert::wrongPeer"]) + .tpush([channelSendCallback::success::noError, "channelSendCallback::success::noError"]) + .tpush([channelSendCallback::revert::error, "channelSendCallback::revert::error"]) + .tpush([deployChannel::success::basic, "deployChannel::success::basic"]) + .tpush([deployConnection::success::basic, "deployConnection::success::basic"]) + .tpush([lzSend::success::noCallNanos, "lzSend::success::noCallNanos"]) + .tpush([lzSend::success::callNanos, "lzSend::success::callNanos"]) + .tpush([lzSend::revert::noPeer, "lzSend::revert::noPeer"]) + .tpush([lzSend::revert::invalidExtraOptions, "lzSend::revert::invalidExtraOptions"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/permissions.fc new file mode 100644 index 00000000..27436ad8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/permissions.fc @@ -0,0 +1,171 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/PacketId.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../protocol/channel/interface.fc"; +#include "../../baseOApp/interface.fc"; +#include "../../../funC++/stdlib.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Counter::permissions"; } + +cell createContractStorage() impure { + setContractStorage( + Counter::New( + getCaller(), + ENDPOINT_ADDRESS, + SRC_EID, + COUNTER_ID, + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE() + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================HELPER FUNCTIONS========================================= + +int getChannelAddress() impure { + cell $channelStorage = Channel::New(getCaller(), MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return _getChannelAddressFromStorageInit($channelStorage); +} + +cell getChannelTestMd() impure inline { + cell $channelStorage = Channel::New(getCaller(), MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return md::ExtendedMd::New(MOCK_SEND_PACKET(), $channelStorage, NULLADDRESS); +} + +;;; ===============================TESTS========================================= + +(int, slice) checkPermissions::lzReceiveExecute::success::basic(cell $storage) impure { + cell $md = getChannelTestMd(); + spoofCaller(getChannelAddress()); + return test::permissions::shouldPass(Layerzero::OP::LZ_RECEIVE_EXECUTE, $md); +} + +(int, slice) checkPermissions::lzReceiveExecute::revert::notChannel(cell $storage) impure { + cell $md = getChannelTestMd(); + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Layerzero::OP::LZ_RECEIVE_EXECUTE, $md); +} + +(int, slice) checkPermissions::channelSendCallback::success::basic(cell $storage) impure { + cell $md = getChannelTestMd(); + spoofCaller(getChannelAddress()); + return test::permissions::shouldPass(Layerzero::OP::CHANNEL_SEND_CALLBACK, $md); +} + +(int, slice) checkPermissions::channelSendCallback::revert::notChannel(cell $storage) impure { + cell $md = getChannelTestMd(); + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Layerzero::OP::CHANNEL_SEND_CALLBACK, $md); +} + +(int, slice) checkPermissions::nilifyCallback::success::basic(cell $storage) impure { + cell $md = getChannelTestMd(); + spoofCaller(getChannelAddress()); + return test::permissions::shouldPass(Layerzero::OP::NILIFY_CALLBACK, $md); +} + +(int, slice) checkPermissions::nilifyCallback::revert::notChannel(cell $storage) impure { + cell $md = getChannelTestMd(); + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Layerzero::OP::NILIFY_CALLBACK, $md); +} + +(int, slice) checkPermissions::deployChannel::success::basic(cell $storage) impure { + return test::permissions::shouldPass(OP::DeployChannel, cl::nullObject()); +} + +(int, slice) checkPermissions::deployChannel::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(OP::DeployChannel, cl::nullObject()); +} + +(int, slice) checkPermissions::setLzConfig::success::basic(cell $storage) impure { + return test::permissions::shouldPass(OP::SetLzConfig, cl::nullObject()); +} + +(int, slice) checkPermissions::setLzConfig::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(OP::SetLzConfig, cl::nullObject()); +} + +(int, slice) checkPermissions::setPeer::success::basic(cell $storage) impure { + return test::permissions::shouldPass(OP::SetPeer, cl::nullObject()); +} + +(int, slice) checkPermissions::setPeer::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(OP::SetPeer, cl::nullObject()); +} + +(int, slice) checkPermissions::setEnforcedOptions::success::basic(cell $storage) impure { + return test::permissions::shouldPass(OP::SetEnforcedOptions, cl::nullObject()); +} + +(int, slice) checkPermissions::setEnforcedOptions::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(OP::SetEnforcedOptions, cl::nullObject()); +} + +(int, slice) checkPermissions::setLzReceivePrepare::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass(Layerzero::OP::LZ_RECEIVE_PREPARE, cl::nullObject()); +} + +(int, slice) checkPermissions::default::revert::invalidOpCode(cell $storage) impure { + ;; this is a generic opcode that we should NOT allow + return test::permissions::shouldFail(OP::RANDOM, cl::nullObject()); +} + +(int, slice) assertChannelAddress::success::basic(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +(int, slice) assertChannelAddress::revert::basic(cell $storage) impure { + return (TEST_SUCCESS, "implement me"); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([checkPermissions::lzReceiveExecute::success::basic, "checkPermissions::lzReceiveExecute::success::basic"]) + .tpush([checkPermissions::lzReceiveExecute::revert::notChannel, "checkPermissions::lzReceiveExecute::revert::notChannel"]) + .tpush([checkPermissions::channelSendCallback::success::basic, "checkPermissions::channelSendCallback::success::basic"]) + .tpush([checkPermissions::channelSendCallback::revert::notChannel, "checkPermissions::channelSendCallback::revert::notChannel"]) + .tpush([checkPermissions::nilifyCallback::success::basic, "checkPermissions::nilifyCallback::success::basic"]) + .tpush([checkPermissions::nilifyCallback::revert::notChannel, "checkPermissions::nilifyCallback::revert::notChannel"]) + .tpush([checkPermissions::deployChannel::success::basic, "checkPermissions::deployChannel::success::basic"]) + .tpush([checkPermissions::deployChannel::revert::notOwner, "checkPermissions::deployChannel::revert::notOwner"]) + .tpush([checkPermissions::setLzConfig::success::basic, "checkPermissions::setLzConfig::success::basic"]) + .tpush([checkPermissions::setLzConfig::revert::notOwner, "checkPermissions::setLzConfig::revert::notOwner"]) + .tpush([checkPermissions::setPeer::success::basic, "checkPermissions::setPeer::success::basic"]) + .tpush([checkPermissions::setPeer::revert::notOwner, "checkPermissions::setPeer::revert::notOwner"]) + .tpush([checkPermissions::setEnforcedOptions::success::basic, "checkPermissions::setEnforcedOptions::success::basic"]) + .tpush([checkPermissions::setEnforcedOptions::revert::notOwner, "checkPermissions::setEnforcedOptions::revert::notOwner"]) + .tpush([checkPermissions::setLzReceivePrepare::success::basic, "checkPermissions::setLzReceivePrepare::success::basic"]) + .tpush([checkPermissions::default::revert::invalidOpCode, "checkPermissions::default::revert::invalidOpCode"]) + .tpush([assertChannelAddress::success::basic, "assertChannelAddress::success::basic"]) + .tpush([assertChannelAddress::revert::basic, "assertChannelAddress::revert::basic"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/setters.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/setters.fc new file mode 100644 index 00000000..6f69b561 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/apps/counter/tests/setters.fc @@ -0,0 +1,258 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/PacketId.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../protocol/channel/interface.fc"; +#include "../../baseOApp/handler.fc"; +#include "../../baseOApp/interface.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../classes/msgdata/MdObj.fc"; +#include "../../../classes/msgdata/OptionsExtended.fc"; +#include "../../../classes/msgdata/OptionsV2.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "counter:setters"; } + +cell createContractStorage() impure { + setContractStorage( + Counter::New( + getCaller(), + CONTROLLER_ADDRESS, + SRC_EID, + COUNTER_ID, + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE() + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================HELPER FUNCTIONS=============================== + +const int COUNTER_NONCE = 1; + +cell abIncrementPacket() impure { + return begin_cell().store_uint8(Counter::increment::ab) + .store_uint32(SRC_EID) + .store_uint32(2) + .end_cell(); +} + +cell _getCounterSendPath() impure { + return lz::Path::New( + SRC_EID, + getContractAddress(), + DST_EID, + DST_OAPP + ); +} + +cell _getCounterReceivePath() impure { + return lz::Path::New( + DST_EID, + DST_OAPP, + SRC_EID, + getContractAddress() + ); +} + +cell _setAndGetPeer(cell $storage, int dstEid, int dstOApp) { + return $storage.setBaseOAppStorage( + getBaseOAppStorage().cl::nestedDict256::set( + BaseOApp::peers, + dstEid, + dstOApp + ) + ); +} + +;;; ===============================TESTS========================================= + +(int, slice) setOwner::success::basic(cell $storage) impure { + ;; update the owner to 'getContractAddress' + cell $baseStorage = $storage.cl::get(Counter::baseStorage); + $baseStorage = $baseStorage.cl::set(BaseStorage::owner, getContractAddress()); + cell $expectedStorage = $storage.cl::set(Counter::baseStorage, $baseStorage); + + return test::handler::shouldPass( + setOwner, + md::SetAddress::New(getContractAddress()), + emptyActions(), + $expectedStorage, + txnContext + ); +} + +(int, slice) setPeer::success::basic(cell $storage) impure { + cell $setPeerMd = md::SetPeer::New(DST_EID, DST_OAPP); + + cell $expectedStorage = $storage._setAndGetPeer(DST_EID, DST_OAPP); + + return test::handler::shouldPass( + setPeer, + $setPeerMd, + unsafeTuple([ + 0, + _newAction(EVENT::PeerSet, $setPeerMd) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setLzConfig::success::basic(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + ;; This can arbitrarily set configs, we are just picking rnadom potential address ie. 'Controller' + cell $path = _getCounterSendPath(); + cell $epConfig = MOCK_EP_CONFIG(true); + cell $configMd = lz::Config::New($path, CONTROLLER_ADDRESS, OP::RANDOM, $epConfig); + + return test::handler::shouldPass( + setLzConfig, + $configMd, + unsafeTuple([ + 0, + _newAction( + CONTROLLER_ADDRESS, + OP::RANDOM, + md::MdObj::New($epConfig, $path) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) setLzConfig::revert::wrongSrcEid(cell $storage) impure { + setContractStorage($storage._setAndGetPeer(DST_EID, DST_OAPP)); + + ;; This can arbitrarily set configs, we are just picking rnadom potential address ie. 'Controller' + cell $path = _getCounterSendPath(); + cell $epConfig = MOCK_EP_CONFIG(true); + cell $configMd = lz::Config::New($path, CONTROLLER_ADDRESS, OP::RANDOM, $epConfig); + + ;; set an invalid srcEid + setContractStorage(getContractStorage().setBaseOAppStorage( + getBaseOAppStorage().cl::set( + BaseOApp::eid, + BAD_EID + ) + )); + + ;; We only need to trigger a single error from in here to validate that this hooks into the verifyPath + return test::handler::shouldFail( + setLzConfig, + $configMd, + ERROR::WrongSrcEid + ); +} + +(int, slice) setEnforcedOptions::success::basic(cell $storage) impure { + cell $optionsExtended = MOCK_OPTIONS_EXTENDED(); + + ;; update the base oApp storage + cell $baseOAppStorage = getBaseOAppStorage() + .cl::nestedDict256::setRef( + BaseOApp::enforcedOptions, + getEnforcedOptionsKey( + $optionsExtended.cl::get(md::OptionsExtended::eid), + $optionsExtended.cl::get(md::OptionsExtended::msgType) + ), + $optionsExtended.cl::get(md::OptionsExtended::options) + ); + + ;; set the expected storage + cell $expectedStorage = $storage.setBaseOAppStorage($baseOAppStorage); + + return test::handler::shouldPass( + setEnforcedOptions, + $optionsExtended, + unsafeTuple([ + 0, + _newAction(EVENT::EnforcedOptionsSet, $optionsExtended) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEnforcedOptions::revert::invalidEnforcedOptions(cell $storage) impure { + cell $optionsExtended = MOCK_OPTIONS_EXTENDED(); + + ;; set the options to an invalid ref, which contains another ref + $optionsExtended = $optionsExtended.cl::set( + md::OptionsExtended::options, + MOCK_OPTIONS_EXTENDED() + ); + + return test::handler::shouldFail( + setEnforcedOptions, + $optionsExtended, + ERROR::InvalidEnforcedOptions + ); +} + +(int, slice) getEnforcedOptions::success::basic(cell $storage) impure { + ;; Set some enforced options + setEnforcedOptions(MOCK_OPTIONS_EXTENDED()); + + ;; get the actual options and test this helper works as intended + cell $optionsExtended = MOCK_OPTIONS_EXTENDED(); + cell $actualOptions = getEnforcedOptions( + $optionsExtended.cl::get(md::OptionsExtended::eid), + $optionsExtended.cl::get(md::OptionsExtended::msgType) + ); + + return test::shouldBeTrue( + objectsAreEqual( + $optionsExtended.cl::get(md::OptionsExtended::options), + $actualOptions) + ); +} + +(int, slice) getEnforcedOptionsKey::success::basic(cell $storage) impure { + cell $optionsExtended = MOCK_OPTIONS_EXTENDED(); + + int key = getEnforcedOptionsKey( + $optionsExtended.cl::get(md::OptionsExtended::eid), + $optionsExtended.cl::get(md::OptionsExtended::msgType) + ); + + ;; 438086664193 == 110011000000000000000000000000000000001 (in binary) == uint32(DST_EID) << 32 | uint32(MSG_TYPE) + return test::shouldBeTrue(key == 438086664193); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([setOwner::success::basic, "setOwner::success::basic"]) + .tpush([setPeer::success::basic, "setPeer::success::basic"]) + .tpush([setLzConfig::success::basic, "setLzConfig::success::basic"]) + .tpush([setLzConfig::revert::wrongSrcEid, "setLzConfig::revert::wrongSrcEid"]) + .tpush([setEnforcedOptions::success::basic, "setEnforcedOptions::success::basic"]) + .tpush([setEnforcedOptions::revert::invalidEnforcedOptions, "setEnforcedOptions::revert::invalidEnforcedOptions"]) + .tpush([getEnforcedOptions::success::basic, "getEnforcedOptions::success::basic"]) + .tpush([getEnforcedOptionsKey::success::basic, "getEnforcedOptionsKey::success::basic"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Config.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Config.fc new file mode 100644 index 00000000..13108c4d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Config.fc @@ -0,0 +1,36 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int lz::Config::NAME = "Config"u; + +;; field names +const int lz::Config::path = 0; +const int lz::Config::forwardingAddress = 1; +const int lz::Config::opCode = 2; +;; provide an arbitrary config md that can be forwarded anywhere in the lz protocol that a config is accepted +const int lz::Config::config = 3; + +cell lz::Config::New(cell $path, int forwardingAddress, int opCode, cell $config) impure inline method_id { + return cl::declare( + lz::Config::NAME, + unsafeTuple([ + [cl::t::objRef, $path], ;; lz::Config::path + [cl::t::address, forwardingAddress], ;; lz::Config::forwardingAddress + [cl::t::uint32, opCode], ;; lz::Config::opCode + [cl::t::objRef, $config] ;; lz::Config::config + ]) + ); +} + +const int lz::Config::_forwardingAddressOffset = _HEADER_WIDTH; +const int lz::Config::_opCodeOffset = lz::Config::_forwardingAddressOffset + 256; + +(cell, int, int, cell) lz::Config::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(0), + selfSlice.preloadAddressAt(lz::Config::_forwardingAddressOffset), + selfSlice.preloadUint32At(lz::Config::_opCodeOffset), + selfSlice.preloadRefAt(1) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/EpConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/EpConfig.fc new file mode 100644 index 00000000..04144ff1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/EpConfig.fc @@ -0,0 +1,158 @@ +#include "../../funC++/classlib.fc"; + +;; ERRORS +const int lz::EpConfig::ERROR::sameMsglib = 1025; +const int lz::EpConfig::ERROR::invalidTimeoutExpiry = 1026; +const int lz::EpConfig::ERROR::invalidTimeoutReceiveMsglib = 1027; +const int lz::EpConfig::VALID = 42069 & ERRORCODE_MASK; + +;; required storage name +const int lz::EpConfig::NAME = "EpConfig"u; + +;; field names +const int lz::EpConfig::isNull = 0; +const int lz::EpConfig::sendMsglibManager = 1; +const int lz::EpConfig::sendMsglib = 2; +const int lz::EpConfig::sendMsglibConnection = 3; +const int lz::EpConfig::receiveMsglib = 4; +const int lz::EpConfig::receiveMsglibConnection = 5; +const int lz::EpConfig::timeoutReceiveMsglib = 6; +const int lz::EpConfig::timeoutReceiveMsglibConnection = 7; +const int lz::EpConfig::timeoutReceiveMsglibExpiry = 8; + +cell lz::EpConfig::NewWithConnection( + int isNull, + int sendMsglibManager, + int sendMsglib, + int sendMsglibConnection, + int receiveMsglib, + int receiveMsglibConnection, + int timeoutReceiveMsglib, + int timeoutReceiveMsglibConnection, + int timeoutReceiveMsglibExpiry +) impure inline method_id { + return cl::declare( + lz::EpConfig::NAME, + unsafeTuple([ + [cl::t::bool, isNull], ;; lz::EpConfig::isNull + [cl::t::address, sendMsglibManager], ;; lz::EpConfig::sendMsglibManager + [cl::t::address, sendMsglib], ;; lz::EpConfig::sendMsglib + [cl::t::address, sendMsglibConnection], ;; lz::EpConfig::sendMsglibConnection + [cl::t::address, receiveMsglib], ;; lz::EpConfig::receiveMsglib + [cl::t::address, receiveMsglibConnection], ;; lz::EpConfig::receiveMsglibConnection + [cl::t::address, timeoutReceiveMsglib], ;; lz::EpConfig::timeoutReceiveMsglib + [cl::t::address, timeoutReceiveMsglibConnection], ;; lz::EpConfig::timeoutReceiveMsglibConnection + [cl::t::uint64, timeoutReceiveMsglibExpiry] ;; lz::EpConfig::timeoutReceiveMsglibExpiry + ]) + ); +} + +cell lz::EpConfig::New( + int isNull, + int sendMsglibManager, + int sendMsglib, + int receiveMsglib, + int timeoutReceiveMsglib, + int timeoutReceiveMsglibExpiry +) impure inline method_id { + return lz::EpConfig::NewWithConnection( + isNull, + sendMsglibManager, + sendMsglib, + NULLADDRESS, + receiveMsglib, + NULLADDRESS, + timeoutReceiveMsglib, + NULLADDRESS, + timeoutReceiveMsglibExpiry + ); +} + +cell lz::EpConfig::NewWithDefaults() impure inline method_id { + return lz::EpConfig::NewWithConnection( + true, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + 0 + ); +} + +;; ====================== Object Multi-Getters ===================== + +;; in root cell +const int lz::EpConfig::_isNullOffset = _HEADER_WIDTH; +const int lz::EpConfig::_sendMsglibManagerOffset = lz::EpConfig::_isNullOffset + 1; +const int lz::EpConfig::_sendMsglibOffset = lz::EpConfig::_sendMsglibManagerOffset + 256; + +;; in ref[2] +const int lz::EpConfig::_sendMsglibConnectionOffset = 0; +const int lz::EpConfig::_receiveMsglibOffset = lz::EpConfig::_sendMsglibConnectionOffset + 256; +const int lz::EpConfig::_receiveMsglibConnectionOffset = lz::EpConfig::_receiveMsglibOffset + 256; + +;; in ref[3] +const int lz::EpConfig::_timeoutReceiveMsglibOffset = 0; +const int lz::EpConfig::_timeoutReceiveMsglibConnectionOffset = lz::EpConfig::_timeoutReceiveMsglibOffset + 256; +const int lz::EpConfig::_timeoutReceiveMsglibExpiryOffset = lz::EpConfig::_timeoutReceiveMsglibConnectionOffset + 256; + +;; (isNull, sendMsglibManager, sendMsglib, sendMsglibConnection) +(int, int, int, int) lz::EpConfig::deserializeSendConfig(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadBoolAt(lz::EpConfig::_isNullOffset), + selfSlice.preloadAddressAt(lz::EpConfig::_sendMsglibManagerOffset), + selfSlice.preloadAddressAt(lz::EpConfig::_sendMsglibOffset), + selfSlice.preloadRefAt(2).cellPreloadAddressAt(lz::EpConfig::_sendMsglibConnectionOffset) + ); +} + +;; (isNull, receiveMsglibConnection) +(int, int) lz::EpConfig::deserializeReceiveConfig(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadBoolAt(lz::EpConfig::_isNullOffset), + selfSlice.preloadRefAt(2).cellPreloadAddressAt(lz::EpConfig::_receiveMsglibConnectionOffset) + ); +} + +;; ====================== Object Validators ===================== + +int lz::EpConfig::isValid(cell $epConfig) impure inline { + int receiveMsglib = $epConfig.cl::get
(lz::EpConfig::receiveMsglib); + int timeoutReceiveMsglib = $epConfig.cl::get
(lz::EpConfig::timeoutReceiveMsglib); + int timeoutReceiveMsglibExpiry = $epConfig.cl::get(lz::EpConfig::timeoutReceiveMsglibExpiry); + + if ((timeoutReceiveMsglib == NULLADDRESS) & (timeoutReceiveMsglibExpiry != 0)) { + ;; If the timeout receive msglib is null, the expiry must be 0 + return lz::EpConfig::ERROR::invalidTimeoutReceiveMsglib; + } elseif ((timeoutReceiveMsglib != NULLADDRESS) & (timeoutReceiveMsglibExpiry <= now())) { + ;; if the timeout receive msglib is not null, the expiry must be in the future + return lz::EpConfig::ERROR::invalidTimeoutExpiry; + } elseif ((receiveMsglib != NULLADDRESS) & (receiveMsglib == timeoutReceiveMsglib)) { + ;; the receive msglib and timeout receive msglib must be different + return lz::EpConfig::ERROR::sameMsglib; + } + + return lz::EpConfig::VALID; +} + +cell lz::EpConfig::sanitize(cell $epConfig) impure { + cell $sanitizedEpConfig = lz::EpConfig::NewWithConnection( + $epConfig.cl::get(lz::EpConfig::isNull), + $epConfig.cl::get
(lz::EpConfig::sendMsglibManager), + $epConfig.cl::get
(lz::EpConfig::sendMsglib), + $epConfig.cl::get
(lz::EpConfig::sendMsglibConnection), + $epConfig.cl::get
(lz::EpConfig::receiveMsglib), + $epConfig.cl::get
(lz::EpConfig::receiveMsglibConnection), + $epConfig.cl::get
(lz::EpConfig::timeoutReceiveMsglib), + $epConfig.cl::get
(lz::EpConfig::timeoutReceiveMsglibConnection), + $epConfig.cl::get(lz::EpConfig::timeoutReceiveMsglibExpiry) + ); + int validity = lz::EpConfig::isValid($sanitizedEpConfig); + throw_if(validity, validity != lz::EpConfig::VALID); + return $sanitizedEpConfig; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/MsglibInfo.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/MsglibInfo.fc new file mode 100644 index 00000000..df218743 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/MsglibInfo.fc @@ -0,0 +1,58 @@ +#include "../../funC++/classlib.fc"; +#include "../../protocol/msglibs/interface.fc"; + +;; required storage name +const int lz::MsglibInfo::NAME = "MsglibInfo"u; + +;; field names +const int lz::MsglibInfo::msglibAddress = 0; +;; connectionCode + connectionInitStorage are used to programmatically derive the connection address +const int lz::MsglibInfo::msglibConnectionCode = 1; +const int lz::MsglibInfo::msglibConnectionInitStorage = 2; + +cell lz::MsglibInfo::New(int msglibAddress, cell msglibConnectionCode, cell msglibConnectionInitStorage) impure inline method_id { + return cl::declare( + lz::MsglibInfo::NAME, + unsafeTuple([ + [cl::t::address, msglibAddress], ;; lz::MsglibInfo::msglibAddress + [cl::t::cellRef, msglibConnectionCode], ;; lz::MsglibInfo::msglibConnectionBytecode + [cl::t::objRef, msglibConnectionInitStorage] ;; lz::MsglibInfo::msglibConnectionInitStorage + ]) + ); +} + +;; ====================== Object Getters ===================== + +;; everything fits in the root cell +const int lz::MsglibInfo::_msglibAddressOffset = _HEADER_WIDTH; + +int lz::MsglibInfo::getMsglibAddress(cell $self) impure inline { + return $self.cellPreloadAddressAt(lz::MsglibInfo::_msglibAddressOffset); +} + +;; ====================== Object Utils ===================== +int lz::MsglibInfo::getMsglibConnectionAddress(cell $msglibInfo, cell $path) impure inline { + ;; Cannot optimize this cl::set because the messagelib interface is specified in terms of + ;; classlib member field idx rather than data/ref offsets + slice msglibInfoSlice = $msglibInfo.begin_parse(); + + cell msglibConnectionCode = msglibInfoSlice.preloadRefAt(0); + cell $msglibConnectionInitStorage = msglibInfoSlice + .preloadRefAt(1) + .cl::set(MsglibConnection::PathFieldIdx, $path); + + return computeContractAddress( + $msglibConnectionInitStorage, + msglibConnectionCode + ); +} + +;; ========================== Sanitize ========================== + +cell lz::MsglibInfo::sanitize(cell $self) impure { + int msglibAddress = $self.cl::get
(lz::MsglibInfo::msglibAddress); + cell msglibConnectionCode = $self.cl::get(lz::MsglibInfo::msglibConnectionCode); + cell msglibConnectionInitStorage = $self.cl::get(lz::MsglibInfo::msglibConnectionInitStorage); + + return lz::MsglibInfo::New(msglibAddress, msglibConnectionCode, msglibConnectionInitStorage); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Packet.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Packet.fc new file mode 100644 index 00000000..09db9462 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Packet.fc @@ -0,0 +1,280 @@ +#include "../../funC++/classlib.fc"; +#include "../../funC++/constants.fc"; + +#include "Path.fc"; + +;; required storage name +const int lz::Packet::NAME = "Packet"u; + +;; field names +const int lz::Packet::path = 0; +const int lz::Packet::message = 1; +const int lz::Packet::nonce = 2; +const int lz::Packet::guid = 3; + +const int lz::Packet::ERROR::INVALID_MESSAGE = 1089; +const int lz::Packet::ERROR::INVALID_NONCE = 1090; +const int lz::Packet::ERROR::INVALID_PACKET_FIELD = 1091; + +const int lz::Packet::MAX_RECEIVE_MESSAGE_CELLS = 32; +const int lz::Packet::MAX_SEND_MESSAGE_CELLS = 255; + +cell lz::Packet::New(cell $path, cell message, int nonce) impure inline method_id { + return cl::declare( + lz::Packet::NAME, + unsafeTuple([ + [cl::t::objRef, $path], ;; lz::Packet::path + [cl::t::cellRef, message], ;; lz::Packet::message + [cl::t::uint64, nonce], ;; lz::Packet::nonce + [cl::t::uint256, 0] ;; lz::Packet::guid + ]) + ); +} + +const int lz::Packet::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 4); +const int lz::Packet::_headerFillerBits = _HEADER_WIDTH - lz::Packet::_headerInfoBits; +const int lz::Packet::_headerInfo = 417359019239977417716476838698419835; + +;; this function is unused by the protocol but will be used by OApps +cell lz::Packet::build(cell $path, cell message, int nonce) impure inline method_id { + return begin_cell() + .store_uint(lz::Packet::_headerInfo, lz::Packet::_headerInfoBits) ;; header info + .store_ones(lz::Packet::_headerFillerBits) ;; header filler + .store_ref($path) ;; path + .store_ref(message) ;; message + .store_uint64(nonce) ;; nonce + .store_uint256(0) ;; guid (default = 0) + .end_cell(); +} + +;; this function is unused by the protocol but will be used by OApps +cell lz::Packet::nonceless(cell $path, cell message) impure inline method_id { + return lz::Packet::build($path, message, 0); +} + +;; ====================== Object Accessors ===================== + +const int lz::Packet::_nonceOffset = _HEADER_WIDTH; +const int lz::Packet::_guidOffset = lz::Packet::_nonceOffset + 64; + +cell lz::Packet::getPath(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +;; this function is unused by the protocol but will be used by OApps +cell lz::Packet::getMessage(cell $self) impure inline { + return $self.cellPreloadRefAt(1); +} + +int lz::Packet::getNonce(cell $self) impure inline { + return $self.cellPreloadUint64At(lz::Packet::_nonceOffset); +} + +int lz::Packet::getGuid(cell $self) impure inline { + return $self.cellPreloadUint256At(lz::Packet::_guidOffset); +} + +;; returns (path, message, nonce, guid) +(cell, cell, int, int) lz::Packet::deserialize(cell $self) impure inline { + slice $selfSlice = $self.begin_parse(); + return ( + $selfSlice.preloadRefAt(0), + $selfSlice.preloadRefAt(1), + $selfSlice.preloadUint64At(lz::Packet::_nonceOffset), + $selfSlice.preloadUint256At(lz::Packet::_guidOffset) + ); +} + +;; ====================== Object Composite Modifiers ===================== + +;; NOTE: this assumes that the placement of the first field is before the second field +cell lz::Packet::replaceTwoFieldsAtOffsets( + cell encodedPacket, + int replacementValue1, + int replacementOffsetBytes1, + int field1Bytes, + int replacementValue2, + int replacementOffsetBytes2, + int field2Bytes +) impure inline method_id { + int field1PosBits = (replacementOffsetBytes1 % MAX_CELL_BYTES) * 8; + int cell1Idx = replacementOffsetBytes1 / MAX_CELL_BYTES; + int field2PosBits = (replacementOffsetBytes2 % MAX_CELL_BYTES) * 8; + int cell2Idx = replacementOffsetBytes2 / MAX_CELL_BYTES; + slice itr = encodedPacket.begin_parse(); + int field1Bits = field1Bytes * 8; + int field2Bits = field2Bytes * 8; + int field1EndPosBits = field1PosBits + field1Bits; + int field2EndPosBits = field2PosBits + field2Bits; + + throw_if( + lz::Packet::ERROR::INVALID_PACKET_FIELD, + (max(field1Bytes, field2Bytes) > MAX_CELL_BYTES) + | (max(field1EndPosBits, field2EndPosBits) > MAX_CELL_WHOLE_BYTE_BITS) + ); + + ;; short-circuit the common case to save gas + if (cell2Idx == 0) { + return begin_cell() + .store_slice(scutfirst(itr, field1PosBits, 0)) + .store_uint(replacementValue1, field1Bits) + .store_slice(subslice( + itr, + field1EndPosBits, + 0, + field2PosBits - field1EndPosBits, + 0 + )) + .store_uint(replacementValue2, field2Bits) + .store_slice(scutlast( + itr, + itr.slice_bits() - field2EndPosBits, + itr.slice_refs() + )) + .end_cell(); + } + + tuple encodedPacketBuilders = empty_tuple(); + + int idx = 0; + + do { + if ((idx == cell1Idx) & (cell1Idx == cell2Idx)) { + slice beforeFirstField = scutfirst(itr, field1PosBits, 0); + + slice betweenFields = subslice( + itr, + (field1PosBits + field1Bits), + 0, + (field2PosBits - field1PosBits - field1Bits), + 0 + ); + + slice afterSecondField = scutlast( + itr, + itr.slice_bits() - (field2Bits + field2PosBits), + itr.slice_refs() + ); + + encodedPacketBuilders~tpush( + begin_cell() + .store_slice(beforeFirstField) + .store_uint(replacementValue1, field1Bits) + .store_slice(betweenFields) + .store_uint(replacementValue2, field2Bits) + .store_slice(afterSecondField) + ); + } elseif (idx == cell1Idx) { + encodedPacketBuilders~tpush( + begin_cell() + .store_slice(scutfirst(itr, field1PosBits, 0)) + .store_uint(replacementValue1, field1Bits) + .store_slice( + scutlast( + itr, + itr.slice_bits() - (field1Bits + field1PosBits), + itr.slice_refs() + ) + ) + ); + } elseif (idx == cell2Idx) { + encodedPacketBuilders~tpush( + begin_cell() + .store_slice(scutfirst(itr, field2PosBits, 0)) + .store_uint(replacementValue2, field2Bits) + .store_slice( + scutlast( + itr, + itr.slice_bits() - (field2Bits + field2PosBits), + itr.slice_refs() + ) + ) + ); + } else { + encodedPacketBuilders~tpush(begin_cell().store_slice(itr)); + } + + if (itr.slice_refs() > 0) { + itr = itr.preload_first_ref().begin_parse(); + } + idx += 1; + } until (idx >= cell2Idx); + + cell curCell = encodedPacketBuilders.at(cell2Idx).end_cell(); + + while (cell2Idx > 0) { + cell2Idx -= 1; + curCell = encodedPacketBuilders.at(cell2Idx).store_ref(curCell).end_cell(); + } + + return curCell; +} + +cell lz::Packet::setNonceAndGuid(cell $self, int nonce, int guid) impure inline { + return begin_cell() + .store_slice($self.begin_parse().scutfirst(lz::Packet::_nonceOffset, 2)) ;; keep the header and the first two refs + .store_uint64(nonce) + .store_uint256(guid) + .end_cell(); +} + +;; ====================== Object Utilities ===================== + +int lz::Packet::calculateGuid(cell $path, int nonce) inline method_id { + (int srcEid, int srcOApp, int dstEid, int dstOApp) = $path.lz::Path::deserialize(); + return keccak256Builder( + begin_cell() + .store_uint64(nonce) + .store_uint32(srcEid) + .store_uint256(srcOApp) + .store_uint32(dstEid) + .store_uint256(dstOApp) + ); +} + +;; ====================== Object Validators ===================== + +;; assumes that the message is a valid single-linked list +int lz::Packet::_messageBytes(cell $self) impure inline { + slice messageSlice = $self.lz::Packet::getMessage().begin_parse(); + (int sliceBits, int sliceRefs) = messageSlice.slice_bits_refs(); + int messageBytes = sliceBits / 8; + while (sliceRefs > 0) { + messageSlice = messageSlice.preload_first_ref().begin_parse(); + (sliceBits, sliceRefs) = messageSlice.slice_bits_refs(); + messageBytes += (sliceBits / 8); + } + return messageBytes; +} + +() lz::Packet::_assertValidLinkedList(cell head, int maxLen) impure inline { + slice messageSlice = head.begin_parse(); + repeat (maxLen) { + (int sliceBits, int sliceRefs) = messageSlice.slice_bits_refs(); + if (sliceRefs == 0) { + throw_if(lz::Packet::ERROR::INVALID_MESSAGE, sliceBits % 8 != 0); + return (); + } else { + throw_if( + lz::Packet::ERROR::INVALID_MESSAGE, + (sliceRefs != 1) | (sliceBits != MAX_CELL_WHOLE_BYTE_BITS) + ); + } + messageSlice = messageSlice.preload_first_ref().begin_parse(); + } + throw(lz::Packet::ERROR::INVALID_MESSAGE); +} + +() lz::Packet::assertValidSendMessage(cell $self) impure inline { + lz::Packet::_assertValidLinkedList( + $self.lz::Packet::getMessage(), + lz::Packet::MAX_SEND_MESSAGE_CELLS + ); +} + +() lz::Packet::assertValidReceiveMessage(cell $self) impure inline { + lz::Packet::_assertValidLinkedList( + $self.lz::Packet::getMessage(), + lz::Packet::MAX_RECEIVE_MESSAGE_CELLS + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Path.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Path.fc new file mode 100644 index 00000000..c6f68b26 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/Path.fc @@ -0,0 +1,116 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int lz::Path::NAME = "path"u; + +;; field names +const int lz::Path::srcEid = 0; +const int lz::Path::srcOApp = 1; +const int lz::Path::dstEid = 2; +const int lz::Path::dstOApp = 3; + +;; In all blockchains with atomic cross-contract call, we can use src/dst/srcOApp/dstOApp +;; because the send channel doesn't exist (it's just a nonce). +;; In TON, we need both send/receive channels, so we use srcOApp/dstOApp to provide +;; a context-free way to refer to the two ends of the channel. +;; The direction is inferred by the context of the contract (send vs receive). +;; The srcOApp is the 256-bit hashpart of a standard address. +cell lz::Path::New(int srcEid, int srcOApp, int dstEid, int dstOApp) impure inline method_id { + return cl::declare( + lz::Path::NAME, + unsafeTuple([ + [cl::t::uint32, srcEid], ;; lz::Path::srcEid + [cl::t::address, srcOApp], ;; lz::Path::srcOApp + [cl::t::uint32, dstEid], ;; lz::Path::dstEid + [cl::t::address, dstOApp] ;; lz::Path::dstOApp + ]) + ); +} + +const int lz::Path::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 4); +const int lz::Path::_headerFillerBits = _HEADER_WIDTH - lz::Path::_headerInfoBits; +const int lz::Path::_headerInfo = 8903714975572488637007080065659; + +;; this function is unused by the protocol but will be used by OApps +cell lz::Path::build(int srcEid, int srcOApp, int dstEid, int dstOApp) impure inline { + return begin_cell() + .store_uint(lz::Path::_headerInfo, lz::Path::_headerInfoBits) ;; header info + .store_ones(lz::Path::_headerFillerBits) ;; header filler + .store_uint32(srcEid) + .store_uint256(srcOApp) + .store_uint32(dstEid) + .store_uint256(dstOApp) + .end_cell(); +} + +cell lz::Path::endpointPath(int srcEid, int dstEid) impure inline { + return lz::Path::New(srcEid, NULLADDRESS, dstEid, NULLADDRESS); +} + +cell lz::Path::reverse(cell $path) inline { + int srcEid = $path.cl::get(lz::Path::srcEid); + int srcOapp = $path.cl::get
(lz::Path::srcOApp); + int dstEid = $path.cl::get(lz::Path::dstEid); + int dstOapp = $path.cl::get
(lz::Path::dstOApp); + return lz::Path::New(dstEid, dstOapp, srcEid, srcOapp); +} + +;; ====================== Object Getters ===================== + +const int lz::Path::_srcEidOffset = _HEADER_WIDTH; +const int lz::Path::_srcOAppOffset = lz::Path::_srcEidOffset + 32; +const int lz::Path::_dstEidOffset = lz::Path::_srcOAppOffset + 256; +const int lz::Path::_dstOAppOffset = lz::Path::_dstEidOffset + 32; + +int lz::Path::getSrcOApp(cell $self) impure inline { + return $self.cellPreloadAddressAt(lz::Path::_srcOAppOffset); +} + +;; ====================== Storage Composite Accessors ===================== + +int lz::Path::getDstEid(cell $self) impure inline { + return $self.cellPreloadUint32At(lz::Path::_dstEidOffset); +} + +;; (srcEid, dstEid) +(int, int) lz::Path::getEidAndDstEid(cell $self) impure inline { + slice $selfSlice = $self.begin_parse(); + return ( + $selfSlice.preloadUint32At(lz::Path::_srcEidOffset), + $selfSlice.preloadUint32At(lz::Path::_dstEidOffset) + ); +} + +;; (srcEid, srcOApp, dstEid, dstOApp) +(int, int, int, int) lz::Path::deserialize(cell $self) impure inline { + slice $selfSlice = $self.begin_parse(); + return ( + $selfSlice.preloadUint32At(lz::Path::_srcEidOffset), + $selfSlice.preloadAddressAt(lz::Path::_srcOAppOffset), + $selfSlice.preloadUint32At(lz::Path::_dstEidOffset), + $selfSlice.preloadAddressAt(lz::Path::_dstOAppOffset) + ); +} + +;; ====================== Object Mutators ===================== + +;; low-level optimized version +;; original: 12k gas +;; optimized: 1k gas +cell lz::Path::optimizedReverse(cell $path) impure inline { + slice pathSlice = $path.begin_parse(); + return begin_cell() + .store_slice(pathSlice.scutfirst(_HEADER_WIDTH, 0)) + .store_slice(pathSlice.preload_bits_offset(lz::Path::_dstEidOffset, 288)) ;; 32 + 256 + .store_slice(pathSlice.preload_bits_offset(lz::Path::_srcEidOffset, 288)) ;; eid + address + .end_cell(); +} + +cell lz::Path::sanitize(cell $path) impure { + return lz::Path::New( + $path.cl::get(lz::Path::srcEid), + $path.cl::get
(lz::Path::srcOApp), + $path.cl::get(lz::Path::dstEid), + $path.cl::get
(lz::Path::dstOApp) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/ReceiveEpConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/ReceiveEpConfig.fc new file mode 100644 index 00000000..500c7803 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/ReceiveEpConfig.fc @@ -0,0 +1,55 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int lz::ReceiveEpConfig::NAME = "RcvEpCfg"u; + +;; field names +const int lz::ReceiveEpConfig::receiveMsglibConnection = 0; +const int lz::ReceiveEpConfig::timeoutReceiveMsglibConnection = 1; +const int lz::ReceiveEpConfig::expiry = 2; + +cell lz::ReceiveEpConfig::New( + int receiveMsglibConnectionAddress, + int timeoutReceiveMsglibConnectionAddress, + int expiry +) impure inline method_id { + return cl::declare( + lz::ReceiveEpConfig::NAME, + unsafeTuple([ + [cl::t::address, receiveMsglibConnectionAddress], ;; lz::ReceiveEpConfig::receiveMsglibConnection + [cl::t::address, timeoutReceiveMsglibConnectionAddress], ;; lz::ReceiveEpConfig::timeoutReceiveMsglibConnection + [cl::t::uint64, expiry] ;; lz::ReceiveEpConfig::expiry + ]) + ); +} + +;; ====================== Object Builders ===================== + + +const int lz::ReceiveEpConfig::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 3); +const int lz::ReceiveEpConfig::_headerFillerBits = _HEADER_WIDTH - lz::ReceiveEpConfig::_headerInfoBits; +const int lz::ReceiveEpConfig::_headerInfo = 106946417840994430288387571463327099; + +cell lz::ReceiveEpConfig::build( + int receiveMsglibConnectionAddress, + int timeoutReceiveMsglibConnectionAddress, + int expiry +) impure inline { + return begin_cell() + .store_uint(lz::ReceiveEpConfig::_headerInfo, lz::ReceiveEpConfig::_headerInfoBits) ;; header info + .store_ones(lz::ReceiveEpConfig::_headerFillerBits) ;; header filler + .store_uint256(receiveMsglibConnectionAddress) ;; cl::t::uint256 + .store_uint256(timeoutReceiveMsglibConnectionAddress) ;; cl::t::uint256 + .store_uint64(expiry) ;; cl::t::uint64 + .end_cell(); +} + +;; ====================== Object Getters ===================== + +const int lz::ReceiveEpConfig::_receiveMsglibConnectionOffset = _HEADER_WIDTH; +const int lz::ReceiveEpConfig::_timeoutReceiveMsglibConnectionOffset = lz::ReceiveEpConfig::_receiveMsglibConnectionOffset + 256; +const int lz::ReceiveEpConfig::_expiryOffset = lz::ReceiveEpConfig::_timeoutReceiveMsglibConnectionOffset + 256; + +int lz::ReceiveEpConfig::getReceiveMsglibConnection(cell $self) impure inline { + return $self.cellPreloadAddressAt(lz::ReceiveEpConfig::_receiveMsglibConnectionOffset); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/SendEpConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/SendEpConfig.fc new file mode 100644 index 00000000..951e00a1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/SendEpConfig.fc @@ -0,0 +1,68 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int lz::SendEpConfig::NAME = "SendEpCfg"u; + +;; field names +const int lz::SendEpConfig::sendMsglibManager = 0; +const int lz::SendEpConfig::sendMsglib = 1; +const int lz::SendEpConfig::sendMsglibConnection = 2; + +cell lz::SendEpConfig::New(int sendMsglibManager, int sendMsglib, int sendMsglibConnection) impure inline method_id { + return cl::declare( + lz::SendEpConfig::NAME, + unsafeTuple([ + [cl::t::address, sendMsglibManager], ;; lz::SendEpConfig::sendMsglibManager + [cl::t::address, sendMsglib], ;; lz::SendEpConfig::sendMsglib + [cl::t::address, sendMsglibConnection] ;; lz::SendEpConfig::sendMsglibConnection + ]) + ); +} + +;; ====================== Object Builders ===================== + +;; everything fits in the root cell +const int lz::SendEpConfig::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 3); +const int lz::SendEpConfig::_headerFillerBits = _HEADER_WIDTH - lz::SendEpConfig::_headerInfoBits; +const int lz::SendEpConfig::_headerInfo = 27713146160555717952182050613837570051; + +cell lz::SendEpConfig::build(int sendMsglibManager, int sendMsglib, int sendMsglibConnection) impure inline { + return begin_cell() + .store_uint(lz::SendEpConfig::_headerInfo, lz::SendEpConfig::_headerInfoBits) + .store_ones(lz::SendEpConfig::_headerFillerBits) + .store_uint256(sendMsglibManager) + .store_uint256(sendMsglib) + .store_ref(empty_cell()) + .store_ref(empty_cell()) + .store_ref( + begin_cell() + .store_uint256(sendMsglibConnection) + .end_cell() + ) + .end_cell(); +} + +;; root cell offsets +const int lz::SendEpConfig::_sendMsglibManagerOffset = _HEADER_WIDTH; +const int lz::SendEpConfig::_sendMsglibOffset = _HEADER_WIDTH + 256; + +;; ref[2] offsets +const int lz::SendEpConfig::_sendMsglibConnectionOffset = 0; + +;; ====================== Object Getters ===================== + +int lz::SendEpConfig::getSendMsglib(cell $self) impure inline { + return $self.cellPreloadAddressAt(lz::SendEpConfig::_sendMsglibOffset); +} + +;; ====================== Object Multi-Getters ===================== + +;; (sendMsglibManager, sendMsglib, sendMsglibConnection) +(int, int, int) lz::SendEpConfig::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadAddressAt(lz::SendEpConfig::_sendMsglibManagerOffset), + selfSlice.preloadAddressAt(lz::SendEpConfig::_sendMsglibOffset), + selfSlice.preloadRefAt(2).cellPreloadAddressAt(lz::SendEpConfig::_sendMsglibConnectionOffset) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/SmlJobAssigned.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/SmlJobAssigned.fc new file mode 100644 index 00000000..ff600c8b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/SmlJobAssigned.fc @@ -0,0 +1,20 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int lz::SmlJobAssigned::NAME = "SmlJobAssg"u; + +;; field names +const int lz::SmlJobAssigned::executorAddress = 0; +const int lz::SmlJobAssigned::fee = 1; + +const int lz::SmlJobAssigned::SmlExecutorAddress = 0xdeadbeef; + +cell lz::SmlJobAssigned::New(int fee) inline method_id { + return cl::declare( + lz::SmlJobAssigned::NAME, + unsafeTuple([ + [cl::t::address, lz::SmlJobAssigned::SmlExecutorAddress], ;; lz::SmlJobAssigned::executorAddress + [cl::t::coins, fee] ;; lz::SmlJobAssigned::fee + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/UlnReceiveConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/UlnReceiveConfig.fc new file mode 100644 index 00000000..69c15291 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/UlnReceiveConfig.fc @@ -0,0 +1,92 @@ +#include "../../../funC++/classlib.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../../tests/testMain.fc"; + +slice _testName() { return "UlnReceiveConfig"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +;;; ===============================TESTS========================================= +(int, slice) validateUlnReceiveConfig::shouldThrow( + cell $UlnReceiveConfig, + int expected_error +) impure { + int failed = false; + try { + UlnReceiveConfig::sanitize($UlnReceiveConfig); + return (TEST_FAILED, "never throws"); + } catch(x, n) { + if (n != expected_error) { + return ( + TEST_FAILED, + "actual error: " + .str::concatInt(n) + .str::concat(" != expected: ") + .str::concatInt(expected_error) + ); + } + } + return test::shouldBeTrue(true); +} + +(int, slice) UlnReceiveConfig::validate::success::defaultAllRequiredDVNs(cell $storage) impure { + UlnReceiveConfig::sanitize(MOCK_DEFAULT_ULN_RECEIVE_CONFIG()); + return test::shouldBeTrue(true); +} + +(int, slice) UlnReceiveConfig::validate::success::customAllRequiredDVNs(cell $storage) impure { + UlnReceiveConfig::sanitize(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(2, 0, 0)); + return test::shouldBeTrue(true); + +} + +(int, slice) UlnReceiveConfig::validate::success::defaultWithOptionalDVNs(cell $storage) impure { + UlnReceiveConfig::sanitize(MOCK_DEFAULT_ULN_RECEIVE_CONFIG_WITH_OPTIONAL_DVNS(2, 2)); + return test::shouldBeTrue(true); +} + +(int, slice) UlnReceiveConfig::validate::success::customWithOptionalDVNs(cell $storage) impure { + UlnReceiveConfig::sanitize(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(2, 2, 2)); + return test::shouldBeTrue(true); +} + +(int, slice) UlnReceiveConfig::validate::fail::thresholdTooHigh(cell $storage) impure { + int optionalDvnCount = 2; + return validateUlnReceiveConfig::shouldThrow( + MOCK_CUSTOM_ULN_RECEIVE_CONFIG(2, optionalDvnCount, optionalDvnCount + 1), + UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LARGE + ); +} + +(int, slice) UlnReceiveConfig::validate::fail::thresholdTooLow(cell $storage) impure { + return validateUlnReceiveConfig::shouldThrow( + MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 1, 0), + UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LOW + ); +} + +(int, slice) UlnReceiveConfig::validate::fail::zeroDVNs(cell $storage) impure { + return validateUlnReceiveConfig::shouldThrow( + MOCK_CUSTOM_ULN_RECEIVE_CONFIG( + 0, + 0, + 0 + ), + UlnReceiveConfig::ERROR::DVN_COUNTS_ALL_NIL + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([UlnReceiveConfig::validate::success::defaultAllRequiredDVNs, "UlnReceiveConfig::validate::success::defaultAllRequiredDVNs"]) + .tpush([UlnReceiveConfig::validate::success::customAllRequiredDVNs, "UlnReceiveConfig::validate::success::customAllRequiredDVNs"]) + .tpush([UlnReceiveConfig::validate::success::defaultWithOptionalDVNs, "UlnReceiveConfig::validate::success::defaultWithOptionalDVNs"]) + .tpush([UlnReceiveConfig::validate::success::customWithOptionalDVNs, "UlnReceiveConfig::validate::success::customWithOptionalDVNs"]) + .tpush([UlnReceiveConfig::validate::fail::thresholdTooHigh, "UlnReceiveConfig::validate::fail::thresholdTooHigh"]) + .tpush([UlnReceiveConfig::validate::fail::thresholdTooLow, "UlnReceiveConfig::validate::fail::thresholdTooLow"]) + .tpush([UlnReceiveConfig::validate::fail::zeroDVNs, "UlnReceiveConfig::validate::fail::zeroDVNs"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/UlnSendConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/UlnSendConfig.fc new file mode 100644 index 00000000..374b5717 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/UlnSendConfig.fc @@ -0,0 +1,85 @@ +#include "../../../protocol/msglibs/ultralightnode/msgdata/UlnSendConfig.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../../tests/testMain.fc"; +#include "../../../funC++/dataStructures/AddressList.fc"; + +slice _testName() { return "UlnSendConfig"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +;;; ===============================TESTS========================================= + +int _dvnListEqual(tuple dvnList, cell serializedDvnList) impure { + slice dvnListSlice = serializedDvnList.begin_parse(); + int idx = 0; + while (idx < dvnList.tlen()) { + int dvnAddress = dvnListSlice~AddressList::next(); + if (dvnAddress != dvnList.int_at(idx)) { + return false; + } + if (dvnListSlice.slice_refs() > dvnListSlice.slice_bits()) { + dvnListSlice = dvnListSlice.preload_first_ref().begin_parse(); + } + idx += 1; + } + return dvnListSlice.slice_empty?(); +} + +(int, slice) _serializeDVNList::success::1DVN(cell $args) impure { + tuple dvns = MOCK_DEFAULT_REQUIRED_DVN_LIST(1, true); + cell serialized = AddressList::serialize(dvns); + return test::shouldBeTrue(_dvnListEqual(dvns, serialized)); +} + +(int, slice) _serializeDVNList::success::2DVN(cell $args) impure { + tuple dvns = MOCK_DEFAULT_REQUIRED_DVN_LIST(2, true); + cell serialized = AddressList::serialize(dvns); + return test::shouldBeTrue(_dvnListEqual(dvns, serialized)); +} + +(int, slice) _serializeDVNList::success::3DVN(cell $args) impure { + tuple dvns = MOCK_DEFAULT_REQUIRED_DVN_LIST(3, true); + cell serialized = AddressList::serialize(dvns); + return test::shouldBeTrue(_dvnListEqual(dvns, serialized)); +} + +(int, slice) _serializeDVNList::success::4DVN(cell $args) impure { + tuple dvns = MOCK_DEFAULT_REQUIRED_DVN_LIST(4, true); + cell serialized = AddressList::serialize(dvns); + return test::shouldBeTrue(_dvnListEqual(dvns, serialized)); +} + +(int, slice) _serializeDVNList::success::5DVN(cell $args) impure { + tuple dvns = MOCK_DEFAULT_REQUIRED_DVN_LIST(5, true); + cell serialized = AddressList::serialize(dvns); + return test::shouldBeTrue(_dvnListEqual(dvns, serialized)); +} + +(int, slice) _serializeDVNList::success::6DVN(cell $args) impure { + tuple dvns = MOCK_DEFAULT_REQUIRED_DVN_LIST(6, true); + cell serialized = AddressList::serialize(dvns); + return test::shouldBeTrue(_dvnListEqual(dvns, serialized)); +} + +(int, slice) _serializeDVNList::success::7DVN(cell $args) impure { + tuple dvns = MOCK_DEFAULT_REQUIRED_DVN_LIST(7, true); + cell serialized = AddressList::serialize(dvns); + return test::shouldBeTrue(_dvnListEqual(dvns, serialized)); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([_serializeDVNList::success::1DVN, "_serializeDVNList::success::1DVN"]) + .tpush([_serializeDVNList::success::2DVN, "_serializeDVNList::success::2DVN"]) + .tpush([_serializeDVNList::success::3DVN, "_serializeDVNList::success::3DVN"]) + .tpush([_serializeDVNList::success::4DVN, "_serializeDVNList::success::4DVN"]) + .tpush([_serializeDVNList::success::5DVN, "_serializeDVNList::success::5DVN"]) + .tpush([_serializeDVNList::success::6DVN, "_serializeDVNList::success::6DVN"]) + .tpush([_serializeDVNList::success::7DVN, "_serializeDVNList::success::7DVN"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/main.fc new file mode 100644 index 00000000..1a71d9c8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/main.fc @@ -0,0 +1,93 @@ +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../funC++/classlib.fc"; +#include "../EpConfig.fc"; +#include "../Path.fc"; +#include "../../../../tests/consts.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "lzClasses"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +;;; ===============================TESTS========================================= + +(int, slice) epConfig::isValid::success::basic(cell $storage) impure { + cell $epConfig = MOCK_EP_CONFIG(true); + + return test::shouldBeTrue( + lz::EpConfig::isValid($epConfig) == lz::EpConfig::VALID + ); +} + +(int, slice) epConfig::isValid::revert::invalidTimeoutExpiry(cell $storage) impure { + cell $epConfig = MOCK_EP_CONFIG(true) + .cl::set(lz::EpConfig::timeoutReceiveMsglibExpiry, (now() - 1)); + + return test::shouldBeTrue( + lz::EpConfig::isValid($epConfig) == lz::EpConfig::ERROR::invalidTimeoutExpiry + ); +} + +(int, slice) epConfig::isValid_invalid::revert::timeoutReceiveMsglib(cell $storage) impure { + cell $epConfig = MOCK_EP_CONFIG(true) + .cl::set(lz::EpConfig::timeoutReceiveMsglib, NULLADDRESS); + + return test::shouldBeTrue( + lz::EpConfig::isValid($epConfig) == lz::EpConfig::ERROR::invalidTimeoutReceiveMsglib + ); +} + +(int, slice) epConfig::isValid::revert::sameMsglib(cell $storage) impure { + cell $epConfig = MOCK_EP_CONFIG(true) + .cl::set(lz::EpConfig::timeoutReceiveMsglib, RECEIVE_MSGLIB_ADDRESS); + + return test::shouldBeTrue( + lz::EpConfig::isValid($epConfig) == lz::EpConfig::ERROR::sameMsglib + ); +} + +(int, slice) epConfig::isValid::success::nullAddresses(cell $storage) impure { + cell $epConfig = MOCK_EP_CONFIG(true) + .cl::set(lz::EpConfig::timeoutReceiveMsglibExpiry, 0) + .cl::set(lz::EpConfig::timeoutReceiveMsglib, NULLADDRESS) + .cl::set(lz::EpConfig::receiveMsglib, NULLADDRESS); + + return test::shouldBeTrue( + lz::EpConfig::isValid($epConfig) == lz::EpConfig::VALID + ); +} + +(int, slice) epConfig::isValid::success::timeoutReceiveMsglibNull(cell $storage) impure { + cell $epConfig = MOCK_EP_CONFIG(true) + .cl::set(lz::EpConfig::timeoutReceiveMsglibExpiry, 0) + .cl::set(lz::EpConfig::timeoutReceiveMsglib, NULLADDRESS); + + return test::shouldBeTrue( + lz::EpConfig::isValid($epConfig) == lz::EpConfig::VALID + ); +} + +(int, slice) epConfig::isValid::success::receiveMsglibNull(cell $storage) impure { + cell $epConfig = MOCK_EP_CONFIG(true) + .cl::set(lz::EpConfig::receiveMsglib, NULLADDRESS); + + return test::shouldBeTrue( + lz::EpConfig::isValid($epConfig) == lz::EpConfig::VALID + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([epConfig::isValid::success::basic, "epConfig::isValid::success::basic"]) + .tpush([epConfig::isValid::revert::invalidTimeoutExpiry, "epConfig::isValid::revert::invalidTimeoutExpiry"]) + .tpush([epConfig::isValid_invalid::revert::timeoutReceiveMsglib, "epConfig::isValid_invalid::revert::timeoutReceiveMsglib"]) + .tpush([epConfig::isValid::revert::sameMsglib, "epConfig::isValid::revert::sameMsglib"]) + .tpush([epConfig::isValid::success::nullAddresses, "epConfig::isValid::success::nullAddresses"]) + .tpush([epConfig::isValid::success::timeoutReceiveMsglibNull, "epConfig::isValid::success::timeoutReceiveMsglibNull"]) + .tpush([epConfig::isValid::success::receiveMsglibNull, "epConfig::isValid::success::receiveMsglibNull"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/serde.fc new file mode 100644 index 00000000..860b1b64 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/lz/tests/serde.fc @@ -0,0 +1,510 @@ +#include "../Packet.fc"; +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/testutils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../protocol/endpoint/storage.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "lzClasses Serde"; } + +;;; ===============================TESTS========================================= + +;; -- Sorting these in the same order that it shows on the github PR. +;; -- The main invariant for all of the [getBool, getData, getRef, multiget] is that +;; none of the fields in the objects that are being checked against each other should have the +;; same value, so we can make sure that a field is not being returned in the place of another. +;; therefore, we should be very careful when using the mocks we've had from before. + + +;; Config: Has 1 multi-getter +(int, slice) Serde::lz::Config::deserialize(cell $unused) impure { + cell $lzConfig = MOCK_RESOLVED_CONFIG(); + + ( + cell $path, + int forwardingAddress, + int opCode, + cell $config + ) = lz::Config::deserialize($lzConfig); + + return test::multiget::equal( + $lzConfig, + unsafeTuple([ + lz::Config::path, + lz::Config::forwardingAddress, + lz::Config::opCode, + lz::Config::config + ]), + unsafeTuple([ + $path, + forwardingAddress, + opCode, + $config + ]) + ); +} + +;; EpConfig: Has 2 multi-getters +;; Has 1 sanitizer +(int, slice) Serde::lz::EpConfig::deserializeSendConfig(cell $unused) impure { + cell $epConfig = MOCK_RESOLVED_EP_CONFIG(true); + + ( + int optimizedIsNull, + int optimizedSendMsglibManager, + int optimizedSendMsglib, + int optimizedSendMsglibConnection + ) = lz::EpConfig::deserializeSendConfig($epConfig); + + + return test::multiget::equal( + $epConfig, + unsafeTuple([ + lz::EpConfig::isNull, + lz::EpConfig::sendMsglibManager, + lz::EpConfig::sendMsglib, + lz::EpConfig::sendMsglibConnection + ]), + unsafeTuple([ + optimizedIsNull, + optimizedSendMsglibManager, + optimizedSendMsglib, + optimizedSendMsglibConnection + ]) + ); +} + +(int, slice) Serde::lz::EpConfig::deserializeReceiveConfig(cell $unused) impure { + cell $epConfig = MOCK_RESOLVED_EP_CONFIG(true); + + ( + int optimizedIsNull, + int optimizedReceiveMsglibConnection + ) = lz::EpConfig::deserializeReceiveConfig($epConfig); + + return test::multiget::equal( + $epConfig, + unsafeTuple([ + lz::EpConfig::isNull, + lz::EpConfig::receiveMsglibConnection + ]), + unsafeTuple([ + optimizedIsNull, + optimizedReceiveMsglibConnection + ]) + ); +} + +(int, slice) Serde::lz::EpConfig::sanitize(cell $unused) impure { + cell $epConfig = MOCK_RESOLVED_EP_CONFIG(true); + + cell $sanitizedEpConfig = lz::EpConfig::sanitize( + _dupWithGarbage($epConfig) + ); + + return test::build::equal( + $epConfig, + $sanitizedEpConfig + ); +} + +;; MsglibInfo: Has 1 getter, +;; Has 1 utils, +;; Has 1 sanitizer +(int, slice) Serde::lz::MsglibInfo::getMsglibAddress(cell $unused) impure { + cell $msglibInfo = MOCK_MSG_LIB_INFO(ULN_MANAGER_ADDRESS); + + return test::getData::equal( + $msglibInfo, + lz::MsglibInfo::getMsglibAddress, + lz::MsglibInfo::msglibAddress + ); +} + +(int, slice) Serde::lz::MsglibInfo::getMsglibConnectionAddress(cell $unused) impure { + cell $msglibInfo = MOCK_MSG_LIB_INFO(ULN_MANAGER_ADDRESS); + cell $path = MOCK_RECEIVE_PATH(); + + int expectedAddress = computeContractAddress( + $msglibInfo + .cl::get(lz::MsglibInfo::msglibConnectionInitStorage) + .cl::set(MsglibConnection::PathFieldIdx, $path), + $msglibInfo.cl::get(lz::MsglibInfo::msglibConnectionCode) + ); + + int address = lz::MsglibInfo::getMsglibConnectionAddress($msglibInfo, $path); + + return test::shouldBeTrue(address == expectedAddress); +} + +(int, slice) Serde::lz::MsglibInfo::sanitize(cell $unused) impure { + cell $msglibInfo = MOCK_MSG_LIB_INFO(ULN_MANAGER_ADDRESS); + + cell $sanitizedMsglibInfo = lz::MsglibInfo::sanitize( + _dupWithGarbage($msglibInfo) + ); + + return test::build::equal( + $msglibInfo, + $sanitizedMsglibInfo + ); +} + +;; Packet: Has 1 builder, +;; Has 3 getters +;; Has 1 multi-getter (deserializer) +(int, slice) Serde::lz::Packet::build(cell $unused) impure { + cell $expectedPacket = lz::Packet::New( + MOCK_SEND_PATH(), + MOCK_MESSAGE(), + NONCE + ); + + cell $packet = lz::Packet::build( + MOCK_SEND_PATH(), + MOCK_MESSAGE(), + NONCE + ); + + return test::build::equal( + $expectedPacket, + $packet + ); +} + +(int, slice) Serde::lz::Packet::nonceless(cell $unused) impure { + cell $expectedPacket = lz::Packet::New( + MOCK_SEND_PATH(), + MOCK_MESSAGE(), + 0 + ); + + cell $packet = lz::Packet::nonceless( + MOCK_SEND_PATH(), + MOCK_MESSAGE() + ); + + return test::build::equal($expectedPacket, $packet); +} + +(int, slice) Serde::lz::Packet::getPath(cell $unused) impure { + cell $packet = MOCK_RECEIVE_PACKET(); + + return test::getRef::equal( + $packet, + lz::Packet::getPath, + lz::Packet::path + ); +} + +(int, slice) Serde::lz::Packet::getMessage(cell $unused) impure { + cell $packet = MOCK_RECEIVE_PACKET(); + + return test::getRef::equal( + $packet, + lz::Packet::getMessage, + lz::Packet::message + ); +} + +(int, slice) Serde::lz::Packet::getNonce(cell $unused) impure { + cell $packet = MOCK_RECEIVE_PACKET(); + + return test::getData::equal( + $packet, + lz::Packet::getNonce, + lz::Packet::nonce + ); +} + +(int, slice) Serde::lz::Packet::getGuid(cell $unused) impure { + int guid = 4591; + cell $packet = MOCK_RECEIVE_PACKET().cl::set(lz::Packet::guid, guid); + + return test::getData::equal( + $packet, + lz::Packet::getGuid, + lz::Packet::guid + ); +} + +(int, slice) Serde::lz::Packet::deserialize(cell $unused) impure { + cell $packet = MOCK_RECEIVE_PACKET(); + + (cell $path, cell message, int nonce, int guid) = lz::Packet::deserialize($packet); + + return test::multiget::equal( + $packet, + unsafeTuple([lz::Packet::path, lz::Packet::message, lz::Packet::nonce, lz::Packet::guid]), + unsafeTuple([$path, message, nonce, guid]) + ); +} + +(int, slice) Serde::lz::Packet::setNonceAndGuid(cell $unused) impure { + int nonce = 100; + int guid = 200; + cell $packet = MOCK_RECEIVE_PACKET() + .cl::set(lz::Packet::nonce, nonce) + .cl::set(lz::Packet::guid, guid); + + return test::set::equal( + $packet, + lz::Packet::setNonceAndGuid($packet, nonce, guid) + ); +} + +;; Path: Has 1 builder +;; Has 1 getter +;; Has 2 multi-getters +;; Has 1 sanitizer +(int, slice) Serde::lz::Path::build(cell $unused) impure { + cell $expectedPath = lz::Path::New( + SRC_EID, + SRC_OAPP, + DST_EID, + DST_OAPP + ); + + cell $path = lz::Path::build( + SRC_EID, + SRC_OAPP, + DST_EID, + DST_OAPP + ); + + return test::build::equal( + $expectedPath, + $path + ); +} + +(int, slice) Serde::lz::Path::getSrcOApp(cell $unused) impure { + cell $path = MOCK_SEND_PATH(); + + return test::getData::equal( + $path, + lz::Path::getSrcOApp, + lz::Path::srcOApp + ); +} + +(int, slice) Serde::lz::Path::getEidAndDstEid(cell $unused) impure { + cell $path = MOCK_SEND_PATH(); + + ( + int srcEid, + int dstEid + ) = lz::Path::getEidAndDstEid($path); + + return test::multiget::equal( + $path, + unsafeTuple([ + lz::Path::srcEid, + lz::Path::dstEid + ]), + unsafeTuple([ + srcEid, + dstEid + ]) + ); +} + +(int, slice) Serde::lz::Path::deserialize(cell $unused) impure { + cell $path = MOCK_SEND_PATH(); + + ( + int srcEid, + int srcOApp, + int dstEid, + int dstOApp + ) = lz::Path::deserialize($path); + + return test::multiget::equal( + $path, + unsafeTuple([ + lz::Path::srcEid, + lz::Path::srcOApp, + lz::Path::dstEid, + lz::Path::dstOApp + ]), + unsafeTuple([ + srcEid, + srcOApp, + dstEid, + dstOApp + ]) + ); +} + +(int, slice) Serde::lz::Path::sanitize(cell $unused) impure { + cell $path = MOCK_SEND_PATH(); + + cell $sanitizedPath = lz::Path::sanitize( + _dupWithGarbage($path) + ); + + return test::build::equal( + $path, + $sanitizedPath + ); +} + +;; ReceiveEpConfig: Has 1 builder, +;; Has 1 getter +(int, slice) Serde::lz::ReceiveEpConfig::build(cell $unused) impure { + cell $config = lz::ReceiveEpConfig::New( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); + + return test::build::equal( + $config, + lz::ReceiveEpConfig::build( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ) + ); +} + +(int, slice) Serde::lz::ReceiveEpConfig::getReceiveMsglibConnection(cell $unused) impure { + cell $storage = MOCK_SML_RECEIVE_EP_CONFIG(); + + return test::getData::equal( + $storage, + lz::ReceiveEpConfig::getReceiveMsglibConnection, + lz::ReceiveEpConfig::receiveMsglibConnection + ); +} + +;; SendEpConfig: Has 1 builder, +;; Has 1 getter, +;; Has 1 multi-getter +(int, slice) Serde::lz::SendEpConfig::build(cell $unused) impure { + return test::build::equal( + lz::SendEpConfig::New( + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + SEND_MSGLIB_CONNECTION_ADDRESS + ), + lz::SendEpConfig::build( + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + SEND_MSGLIB_CONNECTION_ADDRESS + ) + ); +} + +(int, slice) Serde::lz::SendEpConfig::getSendMsglib(cell $unused) impure { + return test::getData::equal( + MOCK_SML_SEND_EP_CONFIG(), + lz::SendEpConfig::getSendMsglib, + lz::SendEpConfig::sendMsglib + ); +} + +(int, slice) Serde::lz::SendEpConfig::deserialize(cell $unused) impure { + cell $config = MOCK_SML_SEND_EP_CONFIG(); + + (int msglibManager, int msgLib, int connection) = lz::SendEpConfig::deserialize($config); + + return test::multiget::equal( + $config, + unsafeTuple([lz::SendEpConfig::sendMsglibManager, lz::SendEpConfig::sendMsglib, lz::SendEpConfig::sendMsglibConnection]), + unsafeTuple([msglibManager, msgLib, connection]) + ); +} + +;; BaseStorage: Has 4 getters + +(int, slice) Serde::BaseStorage::getOwner(cell $unused) impure { + cell $storage = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + cell $baseStorage = $storage.Endpoint::getBaseStorage(); + + return test::getData::equal( + $baseStorage, + BaseStorage::getOwner, + BaseStorage::owner + ); +} + +(int, slice) Serde::BaseStorage::getAuthenticated(cell $unused) impure { + cell $storage = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + cell $baseStorage = $storage.Endpoint::getBaseStorage().cl::set(BaseStorage::authenticated, true); + + return test::getBool::equal( + $baseStorage, + BaseStorage::getAuthenticated, + BaseStorage::authenticated + ); +} + +(int, slice) Serde::BaseStorage::getInitialized(cell $unused) impure { + cell $storage = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + ;; set initialized to true to make sure it's not confused with Authenticated + cell $baseStorage = $storage.Endpoint::getBaseStorage().cl::set(BaseStorage::initialized, true); + + return test::getBool::equal( + $baseStorage, + BaseStorage::getInitialized, + BaseStorage::initialized + ); +} + +(int, slice) Serde::BaseStorage::getInitialStorage(cell $unused) impure { + cell $storage = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + cell $baseStorage = $storage.Endpoint::getBaseStorage(); + + return test::getRef::equal( + $baseStorage, + BaseStorage::getInitialStorage, + BaseStorage::initialStorage + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + ;; -- Config: + .tpush([Serde::lz::Config::deserialize, "Serde::lz::Config::deserialize"]) + ;; -- EpConfig + .tpush([Serde::lz::EpConfig::deserializeSendConfig, "Serde::lz::EpConfig::deserializeSendConfig"]) + .tpush([Serde::lz::EpConfig::deserializeReceiveConfig, "Serde::lz::EpConfig::deserializeReceiveConfig"]) + .tpush([Serde::lz::EpConfig::sanitize, "Serde::lz::EpConfig::sanitize"]) + ;; -- MsglibInfo: + .tpush([Serde::lz::MsglibInfo::getMsglibAddress, "Serde::lz::MsglibInfo::getMsglibAddress"]) + .tpush([Serde::lz::MsglibInfo::getMsglibConnectionAddress, "Serde::lz::MsglibInfo::getMsglibConnectionAddress"]) + .tpush([Serde::lz::MsglibInfo::sanitize, "Serde::lz::MsglibInfo::sanitize"]) + ;; -- Packet: + .tpush([Serde::lz::Packet::build, "Serde::lz::Packet::build"]) + .tpush([Serde::lz::Packet::nonceless, "Serde::lz::Packet::nonceless"]) + .tpush([Serde::lz::Packet::getPath, "Serde::lz::Packet::getPath"]) + .tpush([Serde::lz::Packet::getMessage, "Serde::lz::Packet::getMessage"]) + .tpush([Serde::lz::Packet::getNonce, "Serde::lz::Packet::getNonce"]) + .tpush([Serde::lz::Packet::getGuid, "Serde::lz::Packet::getGuid"]) + .tpush([Serde::lz::Packet::deserialize, "Serde::lz::Packet::deserialize"]) + ;; -- Path: + .tpush([Serde::lz::Path::build, "Serde::lz::Path::build"]) + .tpush([Serde::lz::Path::getSrcOApp, "Serde::lz::Path::getSrcOApp"]) + .tpush([Serde::lz::Path::getEidAndDstEid, "Serde::lz::Path::getEidAndDstEid"]) + .tpush([Serde::lz::Path::deserialize, "Serde::lz::Path::deserialize"]) + .tpush([Serde::lz::Path::sanitize, "Serde::lz::Path::sanitize"]) + .tpush([Serde::lz::Packet::setNonceAndGuid, "Serde::lz::Packet::setNonceAndGuid"]) + ;; -- ReceiveEpConfig: + .tpush([Serde::lz::ReceiveEpConfig::build, "Serde::lz::ReceiveEpConfig::build"]) + .tpush([Serde::lz::ReceiveEpConfig::getReceiveMsglibConnection, "Serde::lz::ReceiveEpConfig::getReceiveMsglibConnection"]) + ;; -- SendEpConfig: + .tpush([Serde::lz::SendEpConfig::build, "Serde::lz::SendEpConfig::build"]) + .tpush([Serde::lz::SendEpConfig::getSendMsglib, "Serde::lz::SendEpConfig::getSendMsglib"]) + .tpush([Serde::lz::SendEpConfig::deserialize, "Serde::lz::SendEpConfig::deserialize"]) + ;; -- BaseStorage: + .tpush([Serde::BaseStorage::getOwner, "Serde::BaseStorage::getOwner"]) + .tpush([Serde::BaseStorage::getAuthenticated, "Serde::BaseStorage::getAuthenticated"]) + .tpush([Serde::BaseStorage::getInitialized, "Serde::BaseStorage::getInitialized"]) + .tpush([Serde::BaseStorage::getInitialStorage, "Serde::BaseStorage::getInitialStorage"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/AddMsglib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/AddMsglib.fc new file mode 100644 index 00000000..d5ee4d9e --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/AddMsglib.fc @@ -0,0 +1,29 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::AddMsglib::NAME = "addMsgLib"u; + +;; field names +const int md::AddMsglib::msglibManagerAddress = 0; +const int md::AddMsglib::dstEid = 1; ;; dstEid is not used by SML, only ULN + +cell md::AddMsglib::New(int msglibManagerAddress, int dstEid) impure inline method_id { + return cl::declare( + md::AddMsglib::NAME, + unsafeTuple([ + [cl::t::address, msglibManagerAddress], ;; md::AddMsglib::msglibManagerAddress + [cl::t::uint32, dstEid] ;; md::AddMsglib::dstEid + ]) + ); +} + +;; Ensure AddMsglib doesn't have garbage bits that would cause undefined behaviors in the protocol +cell md::AddMsglib::sanitize(cell $addMsglib) impure inline_ref { + int msglibManagerAddress = $addMsglib.cl::get
(md::AddMsglib::msglibManagerAddress); + int dstEid = $addMsglib.cl::get(md::AddMsglib::dstEid); + + return md::AddMsglib::New( + msglibManagerAddress, + dstEid + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Bool.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Bool.fc new file mode 100644 index 00000000..592d4f05 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Bool.fc @@ -0,0 +1,16 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::Bool::NAME = "Bool"u; + +;; field names +const int md::Bool::bool = 0; + +cell md::Bool::New(int boolean) impure inline method_id { + return cl::declare( + md::Bool::NAME, + unsafeTuple([ + [cl::t::bool, boolean] ;; md::Bool::success + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/ChannelNonceInfo.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/ChannelNonceInfo.fc new file mode 100644 index 00000000..4084b1cb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/ChannelNonceInfo.fc @@ -0,0 +1,38 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::ChannelNonceInfo::NAME = "cNonceInfo"u; + +;; field names +const int md::ChannelNonceInfo::nonce = 0; +const int md::ChannelNonceInfo::firstUnexecutedNonce = 1; + +cell md::ChannelNonceInfo::New(int nonce, int firstUnexecutedNonce) impure inline method_id { + return cl::declare( + md::ChannelNonceInfo::NAME, + unsafeTuple([ + [cl::t::uint64, nonce], ;; md::ChannelNonceInfo::nonce + [cl::t::uint64, firstUnexecutedNonce] ;; md::ChannelNonceInfo::firstUnexecutedNonce + ]) + ); +} + +;; ====================== Object Getters ===================== + +const int md::ChannelNonceInfo::_nonceOffset = _HEADER_WIDTH; +const int md::ChannelNonceInfo::_firstUnexecutedNonceOffset = md::ChannelNonceInfo::_nonceOffset + 64; + +(int, int) md::ChannelNonceInfo::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint64At(md::ChannelNonceInfo::_nonceOffset), + selfSlice.preloadUint64At(md::ChannelNonceInfo::_firstUnexecutedNonceOffset) + ); +} + +cell md::ChannelNonceInfo::sanitize(cell $self) impure inline_ref { + int nonce = $self.cl::get(md::ChannelNonceInfo::nonce); + int firstUnexecutedNonce = $self.cl::get(md::ChannelNonceInfo::firstUnexecutedNonce); + + return md::ChannelNonceInfo::New(nonce, firstUnexecutedNonce); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/CoinsAmount.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/CoinsAmount.fc new file mode 100644 index 00000000..52caa8b8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/CoinsAmount.fc @@ -0,0 +1,24 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::CoinsAmount::NAME = "coinsAmt"u; + +;; field names +const int md::CoinsAmount::amount = 0; + +cell md::CoinsAmount::New(int amount) impure inline method_id { + return cl::declare( + md::CoinsAmount::NAME, + unsafeTuple([ + [cl::t::coins, amount] ;; md::CoinsAmount::amount + ]) + ); +} + +;; ========================== Sanitize ========================== + +cell md::CoinsAmount::sanitize(cell $self) impure { + int amount = $self.cl::get(md::CoinsAmount::amount); + + return md::CoinsAmount::New(amount); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/CounterIncrement.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/CounterIncrement.fc new file mode 100644 index 00000000..7fd82b15 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/CounterIncrement.fc @@ -0,0 +1,24 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::CounterIncrement::NAME = "countIncr"u; + +;; field names +const int md::CounterIncrement::dstEid = 0; +const int md::CounterIncrement::incrementType = 1; +const int md::CounterIncrement::extraOptions = 2; ;; This is a reference to an 'options' md +const int md::CounterIncrement::nativeFee = 3; +const int md::CounterIncrement::zroFee = 4; + +cell md::CounterIncrement::New(int dstEid, int incrementType, cell $extraOptions, int nativeFee, int zroFee) impure inline method_id { + return cl::declare( + md::CounterIncrement::NAME, + unsafeTuple([ + [cl::t::uint32, dstEid], ;; md::CounterIncrement::dstEid + [cl::t::uint8, incrementType], ;; md::CounterIncrement::incrementType + [cl::t::objRef, $extraOptions], ;; md::CounterIncrement::extraOptions + [cl::t::coins, nativeFee], ;; md::CounterIncrement::nativeFee + [cl::t::coins, zroFee] ;; md::CounterIncrement::zroFee + ]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Deploy.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Deploy.fc new file mode 100644 index 00000000..f8d73081 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Deploy.fc @@ -0,0 +1,51 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::Deploy::NAME = "deploy"u; + +;; field names +const int md::Deploy::initialDeposit = 0; +const int md::Deploy::dstEid = 1; +const int md::Deploy::dstOApp = 2; +const int md::Deploy::extraInfo = 3; + +cell md::Deploy::New(int initialDeposit, int dstEid, int dstOApp) impure inline method_id { + return cl::declare( + md::Deploy::NAME, + unsafeTuple([ + [cl::t::coins, initialDeposit], ;; md::Deploy::initialDeposit + [cl::t::uint32, dstEid], ;; md::Deploy::dstEid + [cl::t::uint256, dstOApp], ;; md::Deploy::dstOApp + [cl::t::objRef, cl::nullObject()] ;; md::Deploy::extraInfo + ]) + ); +} + +cell md::Deploy::NewWithExtraInfo( + int initialDeposit, + int dstEid, + int dstOApp, + cell $extraInfo +) method_id { + return md::Deploy::New(initialDeposit, dstEid, dstOApp) + .cl::set(md::Deploy::extraInfo, $extraInfo); +} + +;; ========================== Sanitize ========================== + +cell md::Deploy::sanitize(cell $self) impure { + int initialDeposit = $self.cl::get(md::Deploy::initialDeposit); + int dstEid = $self.cl::get(md::Deploy::dstEid); + int dstOApp = $self.cl::get(md::Deploy::dstOApp); + + return md::Deploy::New(initialDeposit, dstEid, dstOApp); +} + +cell md::Deploy::NewWithExtraInfo::sanitize(cell $self) impure { + int initialDeposit = $self.cl::get(md::Deploy::initialDeposit); + int dstEid = $self.cl::get(md::Deploy::dstEid); + int dstOApp = $self.cl::get(md::Deploy::dstOApp); + cell $extraInfo = $self.cl::get(md::Deploy::extraInfo); + + return md::Deploy::NewWithExtraInfo(initialDeposit, dstEid, dstOApp, $extraInfo); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/ExtendedMd.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/ExtendedMd.fc new file mode 100644 index 00000000..ac9bfb05 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/ExtendedMd.fc @@ -0,0 +1,71 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::ExtendedMd::NAME = "extendedMd"u; + +;; field names +const int md::ExtendedMd::md = 0; +const int md::ExtendedMd::obj = 1; +const int md::ExtendedMd::forwardingAddress = 2; + +cell md::ExtendedMd::New(cell $md, cell $obj, int forwardingAddress) impure inline method_id { + return cl::declare( + md::ExtendedMd::NAME, + unsafeTuple([ + [cl::t::objRef, $md], ;; md::ExtendedMd::md + [cl::t::objRef, $obj], ;; md::ExtendedMd::obj + [cl::t::address, forwardingAddress] ;; md::ExtendedMd::forwardingAddress + ]) + ); +} + +const int md::ExtendedMd::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 3); +const int md::ExtendedMd::_headerFillerBits = _HEADER_WIDTH - md::ExtendedMd::_headerInfoBits; +const int md::ExtendedMd::_headerInfo = 8632157695553525919024050567799415113083; + +cell md::ExtendedMd::build(cell $md, cell $obj, int forwardingAddress) impure inline { + return begin_cell() + .store_uint(md::ExtendedMd::_headerInfo, md::ExtendedMd::_headerInfoBits) ;; header info + .store_ones(md::ExtendedMd::_headerFillerBits) ;; header filler + .store_ref($md) ;; md::ExtendedMd::md + .store_ref($obj) ;; md::ExtendedMd::obj + .store_uint256(forwardingAddress) ;; md::ExtendedMd::forwardingAddress + .end_cell(); +} + +;; ====================== Object Getters ===================== + +const int md::ExtendedMd::_forwardingAddressOffset = _HEADER_WIDTH; + +cell md::ExtendedMd::getObj(cell $self) impure inline { + return $self.cellPreloadRefAt(1); +} + +;; ====================== Object Multi-Getters ===================== + +(cell, int) md::ExtendedMd::getMdAndForwardingAddress(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(0), + selfSlice.preloadAddressAt(md::ExtendedMd::_forwardingAddressOffset) + ); +} + +(cell, cell, int) md::ExtendedMd::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(0), + selfSlice.preloadRefAt(1), + selfSlice.preloadAddressAt(md::ExtendedMd::_forwardingAddressOffset) + ); +} + +;; ====================== Sanitize ===================== + +cell md::ExtendedMd::sanitize(cell $extendedMd) impure { + cell $md = $extendedMd.cl::get(md::ExtendedMd::md); + cell $obj = $extendedMd.cl::get(md::ExtendedMd::obj); + int forwardingAddress = $extendedMd.cl::get
(md::ExtendedMd::forwardingAddress); + + return md::ExtendedMd::New($md, $obj, forwardingAddress); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/GetMsglibCallback.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/GetMsglibCallback.fc new file mode 100644 index 00000000..711fa783 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/GetMsglibCallback.fc @@ -0,0 +1,18 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::getMsglibInfoCallback::NAME = "getMsgLbCb"u; + +;; field names +const int md::getMsglibInfoCallback::msglibAddress = 0; +const int md::getMsglibInfoCallback::connectionCode = 1; + +cell md::getMsglibInfoCallback::New(int msglibAddress, cell connectionCode) impure inline method_id { + return cl::declare( + md::getMsglibInfoCallback::NAME, + unsafeTuple([ + [cl::t::address, msglibAddress], ;; md::getMsglibInfoCallback::msglibAddress + [cl::t::cellRef, connectionCode] ;; md::getMsglibInfoCallback::connectionCode + ]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/InitEndpoint.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/InitEndpoint.fc new file mode 100644 index 00000000..b5f409ba --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/InitEndpoint.fc @@ -0,0 +1,16 @@ +#include "../../funC++/classlib.fc"; + +;; required md name +const int md::InitEndpoint::NAME = "initEp"u; + +;; field names +const int md::InitEndpoint::channelCode = 0; + +cell md::InitEndpoint::New(cell channelCode) impure inline method_id { + return cl::declare( + md::InitEndpoint::NAME, + unsafeTuple([ + [cl::t::cellRef, channelCode] ;; md::InitEndpoint::channelCode + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/InitSmlConnection.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/InitSmlConnection.fc new file mode 100644 index 00000000..90558481 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/InitSmlConnection.fc @@ -0,0 +1,16 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::InitSmlConnection::NAME = "initSmlCon"u; + +;; field names +const int md::InitSmlConnection::channelAddress = 0; + +cell md::InitSmlConnection::New(int channelAddress) inline method_id { + return cl::declare( + md::InitSmlConnection::NAME, + unsafeTuple([ + [cl::t::address, channelAddress] ;; md::InitSmlConnection::channelAddress + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzReceivePrepare.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzReceivePrepare.fc new file mode 100644 index 00000000..1ee7438c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzReceivePrepare.fc @@ -0,0 +1,38 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::LzReceivePrepare::NAME = "lzrecvprep"u; + +;; field names +const int md::LzReceivePrepare::nonce = 0; +const int md::LzReceivePrepare::nanotons = 1; + +cell md::LzReceivePrepare::New(int nonce, int nanotons) impure method_id { + return cl::declare( + md::LzReceivePrepare::NAME, + unsafeTuple([ + [cl::t::uint64, nonce], ;; md::LzReceivePrepare::nonce + [cl::t::coins, nanotons] ;; md::LzReceivePrepare::nanotons + ]) + ); +} + +;; ====================== Object Getters ===================== + +const int md::LzReceivePrepare::_nonceOffset = _HEADER_WIDTH; +const int md::LzReceivePrepare::_nanotonsOffset = md::LzReceivePrepare::_nonceOffset + 64; + +;; this function is unused by the protocol but will be used by OApps +int md::LzReceivePrepare::getNanotons(cell $self) impure inline { + return $self.cellPreloadCoinsAt(md::LzReceivePrepare::_nanotonsOffset); +} + +;; ====================== Object Multi-Getters ===================== + +(int, int) md::LzReceivePrepare::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint64At(md::LzReceivePrepare::_nonceOffset), + selfSlice.preloadCoinsAt(md::LzReceivePrepare::_nanotonsOffset) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzReceiveStatus.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzReceiveStatus.fc new file mode 100644 index 00000000..480b95c9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzReceiveStatus.fc @@ -0,0 +1,128 @@ +#include "../lz/Packet.fc"; + +;; required storage name +const int md::LzReceiveStatus::NAME = "LzRecvSts"u; + +;; field names +const int md::LzReceiveStatus::success = 0; +const int md::LzReceiveStatus::nonce = 1; +const int md::LzReceiveStatus::value = 2; +const int md::LzReceiveStatus::extraData = 3; +const int md::LzReceiveStatus::reason = 4; +const int md::LzReceiveStatus::sender = 5; +const int md::LzReceiveStatus::packet = 6; +const int md::LzReceiveStatus::executionStatus = 7; + +cell md::LzReceiveStatus::New(int success, int nonce) impure inline method_id { + return cl::declare( + md::LzReceiveStatus::NAME, + unsafeTuple([ + [cl::t::bool, success], ;; md::LzReceiveStatus::success + [cl::t::uint64, nonce], ;; md::LzReceiveStatus::nonce + [cl::t::coins, 0], ;; md::LzReceiveAlert::value + [cl::t::cellRef, empty_cell()], ;; md::LzReceiveAlert::extraData + [cl::t::cellRef, empty_cell()], ;; md::LzReceiveAlert::reason + [cl::t::address, NULLADDRESS], ;; md::LzReceiveAlert::sender + [cl::t::objRef, cl::nullObject()], ;; md::LzReceiveAlert::packet + [cl::t::uint8, 0] ;; md::LzReceiveAlert::executionStatus + ]) + ); +} + +cell md::LzReceiveStatus::NewFull( + int success, + int nonce, + int value, + cell extraData, + cell reason, + int sender, + cell $packet, + int executionStatus +) impure inline method_id { + lz::Packet::_assertValidLinkedList(extraData, lz::Packet::MAX_RECEIVE_MESSAGE_CELLS); + lz::Packet::_assertValidLinkedList(reason, lz::Packet::MAX_RECEIVE_MESSAGE_CELLS); + return cl::declare( + md::LzReceiveStatus::NAME, + unsafeTuple([ + [cl::t::bool, success], ;; md::LzReceiveStatus::success + [cl::t::uint64, nonce], ;; md::LzReceiveStatus::nonce + [cl::t::coins, value], ;; md::LzReceiveAlert::sendRequestId + [cl::t::cellRef, extraData], ;; md::LzReceiveAlert::extraData + [cl::t::cellRef, reason], ;; md::LzReceiveAlert::reason + [cl::t::address, sender], ;; md::LzReceiveAlert::sender + [cl::t::objRef, $packet], ;; md::LzReceiveAlert::packet + [cl::t::uint8, executionStatus] ;; md::LzReceiveAlert::executionStatus + ]) + ); +} + +;; ====================== Object Builders ===================== + +const int md::LzReceiveStatus::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 8); +const int md::LzReceiveStatus::_headerFillerBits = _HEADER_WIDTH - md::LzReceiveStatus::_headerInfoBits; +const int md::LzReceiveStatus::_headerInfo = 31461150238884194531671275676859177915085721713836393515717807231; + +;; this function is unused by the protocol but will be used by OApps +cell md::LzReceiveStatus::build( + int success, + int nonce +) impure inline method_id { + return begin_cell() + .store_uint(md::LzReceiveStatus::_headerInfo, md::LzReceiveStatus::_headerInfoBits) ;; header info + .store_ones(md::LzReceiveStatus::_headerFillerBits) ;; header filler + .store_bool(success) ;; bool[0] + .store_uint64(nonce) ;; uint64[1] + .store_uint128(0) ;; coins[2] + .store_uint256(NULLADDRESS) ;; address[3] + .store_uint8(0) ;; uint8[4] + .store_ref(cl::nullObject()) ;; ref[0] + .store_ref(cl::nullObject()) ;; ref[1] + .store_ref( + begin_cell() + .store_ref(cl::nullObject()) ;; ref[2] + .end_cell() + ) + .end_cell(); +} + + +;; ====================== Object Multi-Getters ===================== + +const int md::LzReceiveStatus::_successOffset = _HEADER_WIDTH; +const int md::LzReceiveStatus::_nonceOffset = md::LzReceiveStatus::_successOffset + 1; +const int md::LzReceiveStatus::_valueOffset = md::LzReceiveStatus::_nonceOffset + 64; +const int md::LzReceiveStatus::_senderOffset = md::LzReceiveStatus::_valueOffset + 128; +const int md::LzReceiveStatus::_executionStatusOffset = md::LzReceiveStatus::_senderOffset + 256; +const int md::LzReceiveStatus::_extraDataOffset = md::LzReceiveStatus::_executionStatusOffset + 8; + +(int, int) md::LzReceiveStatus::getSuccessAndNonce(cell $self) impure inline { + slice $selfSlice = $self.begin_parse(); + return ( + $selfSlice.preloadBoolAt(md::LzReceiveStatus::_successOffset), + $selfSlice.preloadUint64At(md::LzReceiveStatus::_nonceOffset) + ); +} + +;; ========================== Sanitize ========================== + +cell md::LzReceiveStatus::NewFull::sanitize(cell $self) impure { + int success = $self.cl::get(md::LzReceiveStatus::success); + int nonce = $self.cl::get(md::LzReceiveStatus::nonce); + int value = $self.cl::get(md::LzReceiveStatus::value); + cell extraData = $self.cl::get(md::LzReceiveStatus::extraData); + cell reason = $self.cl::get(md::LzReceiveStatus::reason); + int sender = $self.cl::get
(md::LzReceiveStatus::sender); + cell $packet = $self.cl::get(md::LzReceiveStatus::packet); + int executionStatus = $self.cl::get(md::LzReceiveStatus::executionStatus); + + return md::LzReceiveStatus::NewFull( + success, + nonce, + value, + extraData, + reason, + sender, + $packet, + executionStatus + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzSend.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzSend.fc new file mode 100644 index 00000000..62e6ee4e --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/LzSend.fc @@ -0,0 +1,186 @@ +#include "../../funC++/classlib.fc"; +#include "../lz/Packet.fc"; + +;; required storage name +const int md::LzSend::NAME = "lzSend"u; + +;; field names +const int md::LzSend::sendRequestId = 0; +const int md::LzSend::sendMsglibManager = 1; +const int md::LzSend::sendMsglib = 2; +const int md::LzSend::sendMsglibConnection = 3; +const int md::LzSend::packet = 4; +const int md::LzSend::nativeFee = 5; +const int md::LzSend::zroFee = 6; +const int md::LzSend::extraOptions = 7; +const int md::LzSend::enforcedOptions = 8; +const int md::LzSend::callbackData = 9; + +cell md::LzSend::New( + int nativeFee, + int zroFee, + cell $extraOptions, + cell $enforcedOptions, + cell $packet, + cell callbackData +) impure inline method_id { + return cl::declare( + md::LzSend::NAME, + unsafeTuple([ + [cl::t::uint64, 0], ;; md::LzSend::sendRequestId + [cl::t::address, NULLADDRESS], ;; md::LzSend::sendMsglibManager + [cl::t::address, NULLADDRESS], ;; md::lzSend::sendMsglib + [cl::t::address, NULLADDRESS], ;; md::lzSend::sendMsglibConnection + [cl::t::objRef, $packet], ;; md::LzSend::packet + [cl::t::coins, nativeFee], ;; md::LzSend::nativeFee + [cl::t::coins, zroFee], ;; md::LzSend::zroFee + [cl::t::objRef, $extraOptions], ;; md::LzSend::extraOptions + [cl::t::objRef, $enforcedOptions], ;; md::LzSend::enforcedOptions + [cl::t::objRef, callbackData] ;; md::LzSend::callbackData + ]) + ); +} + +const int md::LzSend::_headerPostNameBits = 180; +const int md::LzSend::_headerFillerBits = _HEADER_WIDTH - (md::LzSend::_headerPostNameBits + _NAME_WIDTH); +const int md::LzSend::_headerInfo = 582890735024998957421269964955452773563747974476099581; + +;; ========================== Object Builders ========================== + +;; this function is unused by the protocol but will be used by OApps +cell md::LzSend::build( + int nativeFee, + int zroFee, + cell $extraOptions, + cell $enforcedOptions, + cell $packet, + cell callbackData +) impure inline { + return begin_cell() + .store_uint(md::LzSend::NAME, _NAME_WIDTH) + .store_uint(md::LzSend::_headerInfo, md::LzSend::_headerPostNameBits) + .store_ones(md::LzSend::_headerFillerBits) + .store_uint64(0) ;; sendRequestId + .store_uint256(NULLADDRESS) ;; sendMsglibManager + .store_uint256(NULLADDRESS) ;; sendMsglib + .store_ref($packet) + .store_ref($extraOptions) + .store_ref( + begin_cell() + .store_uint256(NULLADDRESS) ;; sendMsglibConnection + .store_uint128(nativeFee) ;; nativeFee + .store_uint128(zroFee) ;; zroFee + .store_ref($enforcedOptions) ;; enforcedOptions + .store_ref(callbackData) ;; callbackData + .end_cell() + ) + .end_cell(); +} + +;; ====================== Object Accessors ===================== + +;; in root cell +const int md::LzSend::_sendRequestIdffset = _HEADER_WIDTH; +const int md::LzSend::_sendMsglibManagerOffset = md::LzSend::_sendRequestIdffset + 64; +const int md::LzSend::_sendMsglibOffset = md::LzSend::_sendMsglibManagerOffset + 256; + +;; in ref[2] +const int md::LzSend::_sendMsglibConnectionOffset = 0; +const int md::LzSend::_nativeFeeOffset = md::LzSend::_sendMsglibConnectionOffset + 256; +const int md::LzSend::_zroFeeOffset = md::LzSend::_nativeFeeOffset + 128; + +int md::LzSend::getSendRequestId(cell $self) impure inline { + return $self.cellPreloadUint64At(md::LzSend::_sendRequestIdffset); +} + +int md::LzSend::getSendMsglib(cell $self) impure inline { + return $self.cellPreloadAddressAt(md::LzSend::_sendMsglibOffset); +} + +cell md::LzSend::getPacket(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +;; gets the path from the packet inside the LzSend +cell md::LzSend::getPath(cell $self) impure inline { + return $self.cellPreloadRefAt(0).cellPreloadRefAt(0); +} + +;; (requestId, nativeFee, zroFee, extraOptions, enforcedOptions, sendMsglibManager) +(int, int, int, cell, cell, int) md::LzSend::deserializeSendCallback(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2Slice = selfSlice.preloadRefSliceAt(2); + return ( + selfSlice.preloadUint64At(md::LzSend::_sendRequestIdffset), + ref2Slice.preloadCoinsAt(md::LzSend::_nativeFeeOffset), + ref2Slice.preloadCoinsAt(md::LzSend::_zroFeeOffset), + selfSlice.preloadRefAt(1), + ref2Slice.preloadRefAt(0), + selfSlice.preloadAddressAt(md::LzSend::_sendMsglibManagerOffset) + ); +} + +;; (packet, extraOptions, enforcedOptions) +(cell, cell, cell) md::LzSend::getQuoteInformation(cell $self) impure inline { + return ( + $self.cellPreloadRefAt(0), + $self.cellPreloadRefAt(1), + $self.cellPreloadRefAt(2).cellPreloadRefAt(0) + ); +} + +(cell, cell) md::LzSend::getPacketAndCallbackData(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(0), + selfSlice.preloadRefSliceAt(2).preloadRefAt(1) + ); +} + +;; ====================== Object Composite Modifiers ===================== + +const int md::lzSend::requestInfoWidth = _HEADER_WIDTH + 64 + 256 + 256; +;; Can't easily store a slice constant because the header isn't byte-aligned +const int md::lzSend::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 10); +const int md::lzSend::_headerPrefix = 11424049401754228397633815938683071207390004427712457772775726065407; +const int md::lzSend::_headerSuffix = 17331160549995323848587739135; +const int md::lzSend::_headerTrailingBits = _HEADER_WIDTH - 256; + +cell md::LzSend::fillRequestInfo( + cell $self, + int requestId, + int sendMsglibManager, + int sendMsglib, + int sendMsglibConnection +) impure inline method_id { + slice selfslice = $self.begin_parse(); + slice ref2Slice = selfslice.preloadRefSliceAt(2); + + ;; Fill in the fields AND overwrite the entire header to match the expected format + return begin_cell() + .store_uint256(md::lzSend::_headerPrefix) + .store_uint(md::lzSend::_headerSuffix, md::lzSend::_headerTrailingBits) + .store_slice(selfslice.scutfirst(0, 2)) + .store_uint64(requestId) + .store_uint256(sendMsglibManager) + .store_uint256(sendMsglib) + .store_ref( + begin_cell() + .store_uint256(sendMsglibConnection) + .store_slice(ref2Slice.sskipfirst(md::LzSend::_nativeFeeOffset, 0)) + .end_cell() + ) + .end_cell(); +} + +cell md::LzSend::setPacketNonceAndGuid(cell $self, int packetNonce, int packetGuid) impure inline { + slice selfSlice = $self.begin_parse(); + + cell $newPacket = selfSlice~load_ref() + .lz::Packet::setNonceAndGuid(packetNonce, packetGuid); + + return begin_cell() + .store_ref($newPacket) + .store_slice(selfSlice) + .end_cell(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdAddress.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdAddress.fc new file mode 100644 index 00000000..92b08850 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdAddress.fc @@ -0,0 +1,60 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::MdAddress::NAME = "MdAddr"u; + +;; field names +const int md::MdAddress::md = 0; +const int md::MdAddress::address = 1; + +cell md::MdAddress::New(cell $md, int address) inline method_id { + return cl::declare( + md::MdAddress::NAME, + unsafeTuple([ + [cl::t::objRef, $md], ;; md::MdAddress::md + [cl::t::address, address] ;; md::MdAddress::address + ]) + ); +} + +;; ========================== Object Builders ========================== + +const int md::MdAddress::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 2); +const int md::MdAddress::_headerFillerBits = _HEADER_WIDTH - md::MdAddress::_headerInfoBits; +const int md::MdAddress::_headerInfo = 5847545689438192720283003; + +cell md::MdAddress::build(cell $md, int address) impure inline method_id { + return begin_cell() + .store_uint(md::MdAddress::_headerInfo, md::MdAddress::_headerInfoBits) ;; header info + .store_ones(md::MdAddress::_headerFillerBits) ;; header filler + .store_uint256(address) ;; md::MdAddress::address + .store_ref($md) ;; md::MdAddress::md = ref[0] + .end_cell(); +} + +;; ========================== Object Getters ========================== + +const int md::MdAddress::_addressOffset = _HEADER_WIDTH; + +cell md::MdAddress::getMd(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +;; ========================== Object Multi-Getters ========================== + +(cell, int) md::MdAddress::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(0), + selfSlice.preloadAddressAt(md::MdAddress::_addressOffset) + ); +} + +;; ========================== Sanitize ========================== + +cell md::MdAddress::sanitize(cell $self) impure inline_ref { + int address = $self.cl::get(md::MdAddress::address); + cell $md = $self.cl::get(md::MdAddress::md); + + return md::MdAddress::New($md, address); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdEid.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdEid.fc new file mode 100644 index 00000000..b2390b30 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdEid.fc @@ -0,0 +1,26 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::MdEid::NAME = "MdEid"u; + +;; field names +const int md::MdEid::md = 0; +const int md::MdEid::eid = 1; + +cell md::MdEid::New(cell $md, int eid) impure inline method_id { + return cl::declare( + md::MdEid::NAME, + unsafeTuple([ + [cl::t::objRef, $md], ;; md::MdEid::md + [cl::t::uint32, eid] ;; md::MdEid::eid + ]) + ); +} + + +cell md::MdEid::sanitize(cell $mdEid) impure inline_ref { + cell $md = $mdEid.cl::get(md::MdEid::md); + int eid = $mdEid.cl::get(md::MdEid::eid); + + return md::MdEid::New($md, eid); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdObj.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdObj.fc new file mode 100644 index 00000000..afad5edb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MdObj.fc @@ -0,0 +1,58 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::MdObj::NAME = "MdObj"u; + +;; field names +const int md::MdObj::md = 0; +const int md::MdObj::obj = 1; + +cell md::MdObj::New(cell $md, cell $obj) impure inline method_id { + return cl::declare( + md::MdObj::NAME, + unsafeTuple([ + [cl::t::objRef, $md], ;; md::MdObj::md + [cl::t::objRef, $obj] ;; md::MdObj::obj + ]) + ); +} + + +;; ========================== Object Builders ========================== +const int md::MdObj::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 2); +const int md::MdObj::_headerFillerBits = _HEADER_WIDTH - md::MdObj::_headerInfoBits; +const int md::MdObj::_headerInfo = 22842038364999638994941; + +cell md::MdObj::build(cell $md, cell $obj) impure inline { + return begin_cell() + .store_uint(md::MdObj::_headerInfo, md::MdObj::_headerInfoBits) ;; header info + .store_ones(md::MdObj::_headerFillerBits) ;; header filler + .store_ref($md) ;; ref[0] + .store_ref($obj) ;; ref[1] + .end_cell(); +} + +;; ========================== Object Multi-Getters ========================== + +cell md::MdObj::getMd(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +cell md::MdObj::getObj(cell $self) impure inline { + return $self.cellPreloadRefAt(1); +} + +(cell, cell) md::MdObj::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(0), + selfSlice.preloadRefAt(1) + ); +} + +cell md::MdObj::sanitize(cell $mdObj) impure { + cell $md = $mdObj.cl::get(md::MdObj::md); + cell $obj = $mdObj.cl::get(md::MdObj::obj); + + return md::MdObj::New($md, $obj); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MessagingReceipt.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MessagingReceipt.fc new file mode 100644 index 00000000..0f8c35b0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MessagingReceipt.fc @@ -0,0 +1,55 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::MessagingReceipt::NAME = "MsgReceipt"u; + +;; field names +const int md::MessagingReceipt::lzSend = 0; +const int md::MessagingReceipt::nativeFeeActual = 1; +const int md::MessagingReceipt::zroFeeActual = 2; +const int md::MessagingReceipt::errorCode = 3; + +cell md::MessagingReceipt::New(cell $lzSend, int nativeFee, int zroFee, int errorCode) impure inline method_id { + return cl::declare( + md::MessagingReceipt::NAME, + unsafeTuple([ + [cl::t::objRef, $lzSend], ;; md::MessagingReceipt::lzSend + [cl::t::coins, nativeFee], ;; md::MessagingReceipt::nativeFeeActual + [cl::t::coins, zroFee], ;; md::MessagingReceipt::zroFeeActual + [cl::t::uint16, errorCode] ;; md::MessagingReceipt::errorCode + ]) + ); +} + + +;; ========================== Object Builders ========================== + +const int md::MessagingReceipt::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 4); +const int md::MessagingReceipt::_headerFillerBits = _HEADER_WIDTH - md::MessagingReceipt::_headerInfoBits; +const int md::MessagingReceipt::_headerInfo = 1727210451775936897226519655289233983117527419; + +cell md::MessagingReceipt::build(cell $lzSend, int nativeFee, int zroFee, int errorCode) impure inline { + return begin_cell() + .store_uint(md::MessagingReceipt::_headerInfo, md::MessagingReceipt::_headerInfoBits) ;; header info + .store_ones(md::MessagingReceipt::_headerFillerBits) ;; header filler + .store_ref($lzSend) ;; ref[0] + .store_uint128(nativeFee) ;; coins[1] + .store_uint128(zroFee) ;; coins[2] + .store_uint16(errorCode) ;; uint16[3] + .end_cell(); +} + +;; ========================== Object Accessors ========================== + +const int md::MessagingReceipt::_nativeFeeOffset = _HEADER_WIDTH; +const int md::MessagingReceipt::_zroFeeOffset = _HEADER_WIDTH + 128; +const int md::MessagingReceipt::_errorCodeOffset = _HEADER_WIDTH + 256; + +;; this function is unused by the protocol but will be used by OApps +(int, cell) md::MessagingReceipt::getErrorCodeAndLzSend(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint16At(md::MessagingReceipt::_errorCodeOffset), + selfSlice.preloadRefAt(0) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MsglibSendCallback.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MsglibSendCallback.fc new file mode 100644 index 00000000..3ae44dde --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/MsglibSendCallback.fc @@ -0,0 +1,195 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::MsglibSendCallback::NAME = "libSndCb"u; + +;; field names +const int md::MsglibSendCallback::nativeFee = 0; +const int md::MsglibSendCallback::zroFee = 1; +const int md::MsglibSendCallback::lzSend = 2; +const int md::MsglibSendCallback::packetEncoded = 3; +const int md::MsglibSendCallback::payees = 4; +const int md::MsglibSendCallback::nonceByteOffset = 5; +const int md::MsglibSendCallback::nonceBytes = 6; +const int md::MsglibSendCallback::guidByteOffset = 7; +const int md::MsglibSendCallback::guidBytes = 8; +const int md::MsglibSendCallback::msglibSendEvents = 9; +const int md::MsglibSendCallback::errorCode = 10; + +cell md::MsglibSendCallback::New( + int nativeFee, + int zroFee, + cell $lzSend, + cell packetEncoded, + cell payees, + int nonceByteOffset, + int nonceBytes, + int guidByteOffset, + int guidBytes, + cell $msglibSendEvents, + int errorCode +) impure inline method_id { + return cl::declare( + md::MsglibSendCallback::NAME, + unsafeTuple([ + [cl::t::coins, nativeFee], ;; md::MsglibSendCallback::nativeFee + [cl::t::coins, zroFee], ;; md::MsglibSendCallback::zroFee + [cl::t::objRef, $lzSend], ;; md::MsglibSendCallback::lzSend + [cl::t::cellRef, packetEncoded], ;; md::MsglibSendCallback::packetEncoded + [cl::t::cellRef, payees], ;; md::MsglibSendCallback::payees + [cl::t::uint16, nonceByteOffset], ;; md::MsglibSendCallback::nonceByteOffset + [cl::t::uint8, nonceBytes], ;; md::MsglibSendCallback::nonceBytes + [cl::t::uint16, guidByteOffset], ;; md::MsglibSendCallback::guidByteOffset + [cl::t::uint8, guidBytes], ;; md::MsglibSendCallback::guidBytes + [cl::t::objRef, $msglibSendEvents], ;; md::MsglibSendCallback::msglibEvents + [cl::t::uint8, errorCode] ;; md::MsglibSendCallback::errorCode + ]) + ); +} + +;; ========================== Object Builders ========================== + +const int md::MsglibSendCallback::_headerInfoBits = 198; +const int md::MsglibSendCallback::_headerFillerBits = _HEADER_WIDTH - (198 + _NAME_WIDTH); +const int md::MsglibSendCallback::_headerInfo = 177909621499943220462532613625031755041688677811453802367547; + +cell md::MsglibSendCallback::build( + int nativeFee, + int zroFee, + cell $lzSend, + cell packetEncoded, + cell payees, + int nonceByteOffset, + int nonceBytes, + int guidByteOffset, + int guidBytes, + cell $msglibSendEvents, + int errorCode +) impure inline { + return begin_cell() + .store_uint(md::MsglibSendCallback::NAME, _NAME_WIDTH) ;; name + .store_uint(md::MsglibSendCallback::_headerInfo, md::MsglibSendCallback::_headerInfoBits) ;; rest of the header + .store_ones(md::MsglibSendCallback::_headerFillerBits) ;; header filler + .store_uint128(nativeFee) ;; nativeFee + .store_uint128(zroFee) ;; zroFee + .store_uint16(nonceByteOffset) ;; nonceByteOffset + .store_uint8(nonceBytes) ;; nonceBytes + .store_uint16(guidByteOffset) ;; guidByteOffset + .store_uint8(guidBytes) ;; guidBytes + .store_uint8(errorCode) ;; errorCode + .store_ref($lzSend) ;; lzSend + .store_ref(packetEncoded) ;; packetEncoded + .store_ref( + begin_cell() + .store_ref(payees) ;; payees + .store_ref($msglibSendEvents) ;; msglibSendEvents + .end_cell() + ) + .end_cell(); +} + +;; ========================== Object Getters ========================== + +const int md::MsglibSendCallback::_nativeFeeOffset = _HEADER_WIDTH; +const int md::MsglibSendCallback::_zroFeeOffset = md::MsglibSendCallback::_nativeFeeOffset + 128; +const int md::MsglibSendCallback::_nonceByteOffsetOffset = md::MsglibSendCallback::_zroFeeOffset + 128; +const int md::MsglibSendCallback::_nonceBytesOffset = md::MsglibSendCallback::_nonceByteOffsetOffset + 16; +const int md::MsglibSendCallback::_guidByteOffsetOffset = md::MsglibSendCallback::_nonceBytesOffset + 8; +const int md::MsglibSendCallback::_guidBytesOffset = md::MsglibSendCallback::_guidByteOffsetOffset + 16; +const int md::MsglibSendCallback::_errorCodeOffset = md::MsglibSendCallback::_guidBytesOffset + 8; + + +cell md::MsglibSendCallback::getLzSend(cell $self) impure inline { + return $self + .begin_parse() + .preload_ref_at(0); +} + +;; ========================== Object Multi-Getters ========================== + +;; (errorCode, nativeFee, zroFee, lzSend, payees, encodedPacket, nonceByteOffset, nonceBytes, guidByteOffset, guidBytes, sendEvents) +(int, int, int, cell, cell, cell, int, int, int, int, cell) md::MsglibSendCallback::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2 = selfSlice.preloadRefSliceAt(2); + + return ( + selfSlice.preloadUint8At(md::MsglibSendCallback::_errorCodeOffset), ;; errorCode + selfSlice.preloadCoinsAt(md::MsglibSendCallback::_nativeFeeOffset), ;; nativeFee + selfSlice.preloadCoinsAt(md::MsglibSendCallback::_zroFeeOffset), ;; zroFee + selfSlice.preloadRefAt(0), ;; lzSend + ref2.preloadRefAt(0), ;; payees + selfSlice.preloadRefAt(1), ;; encodedPacket + selfSlice.preloadUint16At(md::MsglibSendCallback::_nonceByteOffsetOffset),;; nonceByteOffset + selfSlice.preloadUint8At(md::MsglibSendCallback::_nonceBytesOffset), ;; nonceBytes + selfSlice.preloadUint16At(md::MsglibSendCallback::_guidByteOffsetOffset), ;; guidByteOffset + selfSlice.preloadUint8At(md::MsglibSendCallback::_guidBytesOffset), ;; guidBytes + ref2.preloadRefAt(1) + ); +} + +;; ========================== Payees Utilities ========================== + +const int payeesTuple::_addressIdx = 0; +const int payeesTuple::_nativeAmountIdx = 1; +const int payees::_addressBits = 256; +const int payees::_nativeAmountBits = 64; +const int payees::_payeeBits = payees::_addressBits + payees::_nativeAmountBits; + +;; Serializes 3 payees (256-bit address => 64-bit TON coin amount) per cell. +cell serializePayees(tuple payeesInfo) impure inline { + int numPayees = payeesInfo.tlen(); + if (numPayees == 0) { + return empty_cell(); + } + + builder linkedList = begin_cell(); + tuple curPayee = empty_tuple(); + int idx = 1; + while (idx <= numPayees) { + curPayee = payeesInfo.tuple_at(numPayees - idx); + if (idx % 3 == 0) { + linkedList = begin_cell() + .store_ref(linkedList.end_cell()) + .store_uint256(curPayee.int_at(payeesTuple::_addressIdx)) + .store_uint64(curPayee.int_at(payeesTuple::_nativeAmountIdx)); + } else { + linkedList = linkedList + .store_uint256(curPayee.int_at(payeesTuple::_addressIdx)) + .store_uint64(curPayee.int_at(payeesTuple::_nativeAmountIdx)); + } + idx += 1; + } + + return linkedList.end_cell(); +} + +;; deserializePayees will ignore any bits beyond 960 +tuple deserializePayees(cell serializedPayees) impure inline { + slice payeesSlice = serializedPayees.begin_parse(); + if (payeesSlice.slice_empty?()) { + return empty_tuple(); + } + tuple payees = empty_tuple(); + while (~ payeesSlice.slice_empty?()) { + payees = payees.tpush([payeesSlice~load_uint256(), payeesSlice~load_uint64()]); + + ;; can you extract a second one? + if (payeesSlice.slice_bits() >= (payees::_payeeBits)) { + payees = payees.tpush([payeesSlice~load_uint256(), payeesSlice~load_uint64()]); + } + + ;; how about a third? + if (payeesSlice.slice_bits() >= (payees::_payeeBits)) { + payees = payees.tpush([payeesSlice~load_uint256(), payeesSlice~load_uint64()]); + } + + if (payeesSlice.slice_refs() > 0) { + payeesSlice = payeesSlice.preload_first_ref().begin_parse(); + } + } + return payees; +} + +;; Pop the last payee off the output of deserializePayees +;; and return the modified payee list and the popped payee. +(tuple, [int, int]) tpopPayee(tuple t) asm "TPOP"; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Nonce.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Nonce.fc new file mode 100644 index 00000000..01016908 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/Nonce.fc @@ -0,0 +1,46 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::Nonce::NAME = "nonce"u; + +;; field names +const int md::Nonce::nonce = 0; + +cell md::Nonce::New(int nonce) impure inline method_id { + return cl::declare( + md::Nonce::NAME, + unsafeTuple([ + [cl::t::uint64, nonce] ;; md::Nonce::nonce + ]) + ); +} + +;; ========================== Object Builders ========================== +const int md::Nonce::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 1); +const int md::Nonce::_headerFillerBits = _HEADER_WIDTH - md::Nonce::_headerInfoBits; +const int md::Nonce::_headerInfo = 124339069371385211; + +;; this function is unused by the protocol but will be used by OApps +cell md::Nonce::build(int nonce) impure inline { + return begin_cell() + .store_uint(md::Nonce::_headerInfo, md::Nonce::_headerInfoBits) ;; header info + .store_ones(md::Nonce::_headerFillerBits) ;; header filler + .store_uint64(nonce) + .end_cell(); +} + + +;; ========================== Object Getters ========================== +const int md::Nonce::_nonceOffset = _HEADER_WIDTH; + +int md::Nonce::getNonce(cell $self) impure inline { + return $self.cellPreloadUint64At(md::Nonce::_nonceOffset); +} + +;; ========================== Sanitize ========================== + +cell md::Nonce::sanitize(cell $self) impure inline_ref { + int nonce = $self.cl::get(md::Nonce::nonce); + + return md::Nonce::New(nonce); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsExtended.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsExtended.fc new file mode 100644 index 00000000..49a582ff --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsExtended.fc @@ -0,0 +1,32 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::OptionsExtended::NAME = "OptionsExt"u; + +;; field names +const int md::OptionsExtended::eid = 0; +const int md::OptionsExtended::msgType = 1; +const int md::OptionsExtended::options = 2; + +cell md::OptionsExtended::New(int eid, int msgType, cell $options) impure inline method_id { + return cl::declare( + md::OptionsExtended::NAME, + unsafeTuple([ + [cl::t::uint32, eid], ;; md::OptionsExtended::eid + [cl::t::uint32, msgType], ;; md::OptionsExtended::msgType + [cl::t::objRef, $options] ;; md::OptionsExtended::options + ]) + ); +} + +const int md::OptionsExtended::_eidOffset = _HEADER_WIDTH; +const int md::OptionsExtended::_msgTypeOffset = md::OptionsExtended::_eidOffset + 32; + +(int, int, cell) md::OptionsExtended::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint32At(md::OptionsExtended::_eidOffset), + selfSlice.preloadUint32At(md::OptionsExtended::_msgTypeOffset), + selfSlice.preloadRefAt(0) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsV1.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsV1.fc new file mode 100644 index 00000000..0cc628d7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsV1.fc @@ -0,0 +1,51 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::OptionsV1::NAME = "OptionsV1"u; + +;; field names +const int md::OptionsV1::lzReceiveGas = 0; +const int md::OptionsV1::lzReceiveValue = 1; +const int md::OptionsV1::nativeDropAddress = 2; +const int md::OptionsV1::nativeDropAmount = 3; + +cell md::OptionsV1::New( + int lzReceiveGas, + int lzReceiveValue, + int nativeDropAddress, + int nativeDropAmount +) impure inline method_id { + return cl::declare( + md::OptionsV1::NAME, + unsafeTuple([ + [cl::t::uint256, lzReceiveGas], ;; md::OptionsV1::lzReceiveGas + [cl::t::uint256, lzReceiveValue], ;; md::OptionsV1::lzReceiveValue + [cl::t::address, nativeDropAddress], ;; md::OptionsV1::nativeDropAddress + [cl::t::uint256, nativeDropAmount] ;; md::OptionsV1::nativeDropAmount + ]) + ); +} + +;; ========================== Object Multi-Getters ========================== + +const int md::OptionsV1::_lzReceiveGasOffset = _HEADER_WIDTH; +const int md::OptionsV1::_lzReceiveValueOffset = md::OptionsV1::_lzReceiveGasOffset + 256; + +const int md::OptionsV1::_nativeDropAddressOffset = 0; +const int md::OptionsV1::_nativeDropAmountOffset = md::OptionsV1::_nativeDropAddressOffset + 256; + + +int md::OptionsV1::getLzReceiveGas(cell $self) impure inline { + return $self.cellPreloadUint256At(md::OptionsV1::_lzReceiveGasOffset); +} + +;; (lzReceiveGas, lzReceiveValue, nativeDropAmount) +(int, int, int) md::OptionsV1::decodeCoins(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2 = selfSlice.preloadRefSliceAt(2); + return ( + selfSlice.preloadUint256At(md::OptionsV1::_lzReceiveGasOffset), + selfSlice.preloadUint256At(md::OptionsV1::_lzReceiveValueOffset), + ref2.preloadUint256At(md::OptionsV1::_nativeDropAmountOffset) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsV2.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsV2.fc new file mode 100644 index 00000000..de93b99f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/OptionsV2.fc @@ -0,0 +1,64 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::OptionsV2::NAME = "OptionsV2"u; + +;; field names +const int md::OptionsV2::lzReceiveGas = 0; +const int md::OptionsV2::lzReceiveValue = 1; +const int md::OptionsV2::lzComposeGas = 2; +const int md::OptionsV2::lzComposeValue = 3; +const int md::OptionsV2::nativeDropAddress = 4; +const int md::OptionsV2::nativeDropAmount = 5; + +;; This is used to demonstrate that we are capable of having different options objects +;; This is not finalized and the 'V2' shouldnt be considered a 'valid' options type in the protocol YET +;; This is used for testing purposes currently + +cell md::OptionsV2::New( + int lzReceiveGas, + int lzReceiveValue, + int lzComposeGas, + int lzComposeValue, + int nativeDropAddress, + int nativeDropAmount +) impure inline method_id { + return cl::declare( + md::OptionsV2::NAME, + unsafeTuple([ + [cl::t::uint256, lzReceiveGas], ;; md::OptionsV2::lzReceiveGas + [cl::t::uint256, lzReceiveValue], ;; md::OptionsV2::lzReceiveValue + [cl::t::uint256, lzComposeGas], ;; md::OptionsV2::lzComposeGas + [cl::t::uint256, lzComposeValue], ;; md::OptionsV2::lzComposeValue + [cl::t::address, nativeDropAddress], ;; md::OptionsV2::nativeDropAddress + [cl::t::uint256, nativeDropAmount] ;; md::OptionsV2::nativeDropAmount + ]) + ); +} + +;; ========================== Object Multi-Getters ========================== + +const int md::OptionsV2::_lzReceiveGasOffset = _HEADER_WIDTH; +const int md::OptionsV2::_lzReceiveValueOffset = md::OptionsV2::_lzReceiveGasOffset + 256; + +const int md::OptionsV2::_lzComposeGasOffset = 0; +const int md::OptionsV2::_lzComposeValueOffset = md::OptionsV2::_lzComposeGasOffset + 256; +const int md::OptionsV2::_nativeDropAddressOffset = md::OptionsV2::_lzComposeValueOffset + 256; + +const int md::OptionsV2::_nativeDropAmountOffset = 0; + +;; (lzComposeGas, lzComposeValue, lzReceiveGas, lzReceiveValue, nativeDropAmount) +(int, int, int, int, int) md::OptionsV2::decodeCoins(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + + slice ref2 = selfSlice.preloadRefSliceAt(2); + slice ref3 = selfSlice.preloadRefSliceAt(3); + + return ( + ref2.preloadUint256At(md::OptionsV2::_lzComposeGasOffset), + ref2.preloadUint256At(md::OptionsV2::_lzComposeValueOffset), + selfSlice.preloadUint256At(md::OptionsV2::_lzReceiveGasOffset), + selfSlice.preloadUint256At(md::OptionsV2::_lzReceiveValueOffset), + ref3.preloadUint256At(md::OptionsV2::_nativeDropAmountOffset) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/PacketId.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/PacketId.fc new file mode 100644 index 00000000..4bf49d4d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/PacketId.fc @@ -0,0 +1,42 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::PacketId::NAME = "pktId"u; + +;; field names +const int md::PacketId::path = 0; +const int md::PacketId::nonce = 1; + +cell md::PacketId::New(cell $path, int nonce) impure inline method_id { + return cl::declare( + md::PacketId::NAME, + unsafeTuple([ + [cl::t::objRef, $path], ;; md::PacketId::path + [cl::t::uint64, nonce] ;; md::PacketId::nonce + ]) + ); +} + +;; ========================== Object Builders ========================== + +const int md::PacketId::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 2); +const int md::PacketId::_headerFillerBits = _HEADER_WIDTH - md::PacketId::_headerInfoBits; +const int md::PacketId::_headerInfo = 33180451689778480514427; + +cell md::PacketId::build(cell $path, int nonce) impure inline { + return begin_cell() + .store_uint(md::PacketId::_headerInfo, md::PacketId::_headerInfoBits) ;; header info + .store_ones(md::PacketId::_headerFillerBits) ;; header filler + .store_ref($path) ;; ref[0] + .store_uint(nonce, 64) ;; uint[1] + .end_cell(); +} + +;; ========================== Sanitize ========================== + +cell md::PacketId::sanitize(cell $self) impure { + cell $path = $self.cl::get(md::PacketId::path); + int nonce = $self.cl::get(md::PacketId::nonce); + + return md::PacketId::New($path, nonce); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/PacketSent.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/PacketSent.fc new file mode 100644 index 00000000..7f9a85a9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/PacketSent.fc @@ -0,0 +1,73 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::PacketSent::NAME = "pktSent"u; + +;; field names +const int md::PacketSent::nativeFee = 0; +const int md::PacketSent::zroFee = 1; +const int md::PacketSent::extraOptions = 2; +const int md::PacketSent::enforcedOptions = 3; +const int md::PacketSent::packetEncoded = 4; +const int md::PacketSent::nonce = 5; +const int md::PacketSent::msglibAddress = 6; +const int md::PacketSent::msglibSendEvents = 7; + +cell md::PacketSent::New( + int nativeFee, + int zroFee, + cell $extraOptions, + cell $enforcedOptions, + cell packetEncoded, + int nonce, + int msglibAddress, + cell $msglibSendEvents +) impure inline method_id { + return cl::declare( + md::PacketSent::NAME, + unsafeTuple([ + [cl::t::coins, nativeFee], ;; md::PacketSent::nativeFee + [cl::t::coins, zroFee], ;; md::PacketSent::zroFee + [cl::t::objRef, $extraOptions], ;; md::PacketSent::extraOptions + [cl::t::objRef, $enforcedOptions], ;; md::PacketSent::enforcedOptions + [cl::t::cellRef, packetEncoded], ;; md::PacketSent::packetEncoded + [cl::t::uint64, nonce], ;; md::PacketSent::nonce + [cl::t::address, msglibAddress], ;; md::PacketSent::msglibAddress + [cl::t::objRef, $msglibSendEvents] ;; md::PacketSent::msglibEvents + ]) + ); +} + +;; ========================== Object Builders ========================== + +const int md::PacketSent::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 8); +const int md::PacketSent::_headerFillerBits = _HEADER_WIDTH - md::PacketSent::_headerInfoBits; +const int md::PacketSent::_headerInfo = 705670168524170966093988960735291442056898952154447392698365; + +cell md::PacketSent::build( + int nativeFee, + int zroFee, + cell $extraOptions, + cell $enforcedOptions, + cell packetEncoded, + int nonce, + int msglibAddress, + cell $msglibSendEvents +) impure inline { + return begin_cell() + .store_uint(md::PacketSent::_headerInfo, md::PacketSent::_headerInfoBits) + .store_ones(md::PacketSent::_headerFillerBits) + .store_uint128(nativeFee) + .store_uint128(zroFee) + .store_uint64(nonce) + .store_uint256(msglibAddress) + .store_ref($extraOptions) + .store_ref($enforcedOptions) + .store_ref( + begin_cell() + .store_ref(packetEncoded) + .store_ref($msglibSendEvents) + .end_cell() + ) + .end_cell(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetAddress.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetAddress.fc new file mode 100644 index 00000000..b6ed48b4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetAddress.fc @@ -0,0 +1,23 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::SetAddress::NAME = "setAddress"u; + +;; field names +const int md::SetAddress::address = 0; + +cell md::SetAddress::New(int address) impure inline method_id { + return cl::declare( + md::SetAddress::NAME, + unsafeTuple([ + [cl::t::address, address] ;; md::SetAddress::owner + ]) + ); +} + +;; Ensure SetAddress doesn't have garbage bits that would cause undefined behaviors in the protocol +cell md::SetAddress::sanitize(cell $setAddress) impure { + return md::SetAddress::New( + $setAddress.cl::get
(md::SetAddress::address) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetEpConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetEpConfig.fc new file mode 100644 index 00000000..b860d7b0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetEpConfig.fc @@ -0,0 +1,50 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::SetEpConfig::NAME = "SetEpCfg"u; + +;; 'md::SetEpConfig::useDefaults' is ONLY used during the oapp config set flow. +;; When setting 'epConfigDefaults' as the owner, the flag doesnt matter, because call is for defaults + +;; field names +const int md::SetEpConfig::useDefaults = 0; +const int md::SetEpConfig::sendMsglibManager = 1; +const int md::SetEpConfig::receiveMsglibManager = 2; +const int md::SetEpConfig::timeoutReceiveMsglibManager = 3; +const int md::SetEpConfig::timeoutReceiveMsglibExpiry = 4; + +cell md::SetEpConfig::New( + int useDefaults, + int sendMsglibManager, + int receiveMsglibManager, + int timeoutReceiveMsglibManager, + int timeoutReceiveMsglibExpiry +) impure inline method_id { + return cl::declare( + md::SetEpConfig::NAME, + unsafeTuple([ + [cl::t::bool, useDefaults], ;; md::SetEpConfig::useDefaults + [cl::t::address, sendMsglibManager], ;; md::SetEpConfig::sendMsglibManager + [cl::t::address, receiveMsglibManager], ;; md::SetEpConfig::receiveMsglibManager + [cl::t::address, timeoutReceiveMsglibManager], ;; md::SetEpConfig::timeoutReceiveMsglibManager + [cl::t::uint64, timeoutReceiveMsglibExpiry] ;; md::SetEpConfig::timeoutReceiveMsglibExpiry + ]) + ); +} + +;; Ensure SetEpConfig doesn't have garbage bits that would cause undefined behaviors in the protocol +cell md::SetEpConfig::sanitize(cell $setEpConfig) impure inline_ref { + int useDefaults = $setEpConfig.cl::get(md::SetEpConfig::useDefaults); + int sendMsglibManager = $setEpConfig.cl::get
(md::SetEpConfig::sendMsglibManager); + int receiveMsglibManager = $setEpConfig.cl::get
(md::SetEpConfig::receiveMsglibManager); + int timeoutReceiveMsglibManager = $setEpConfig.cl::get
(md::SetEpConfig::timeoutReceiveMsglibManager); + int timeoutReceiveMsglibExpiry = $setEpConfig.cl::get(md::SetEpConfig::timeoutReceiveMsglibExpiry); + + return md::SetEpConfig::New( + useDefaults, + sendMsglibManager, + receiveMsglibManager, + timeoutReceiveMsglibManager, + timeoutReceiveMsglibExpiry + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetPeer.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetPeer.fc new file mode 100644 index 00000000..a4b643c0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetPeer.fc @@ -0,0 +1,18 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::SetPeer::NAME = "setPeer"u; + +;; field names +const int md::SetPeer::eid = 0; +const int md::SetPeer::peer = 1; + +cell md::SetPeer::New(int eid, int peer) impure inline method_id { + return cl::declare( + md::SetPeer::NAME, + unsafeTuple([ + [cl::t::uint32, eid], ;; md::SetPeer::eid + [cl::t::uint256, peer] ;; md::SetPeer::peer + ]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetSmlManagerConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetSmlManagerConfig.fc new file mode 100644 index 00000000..480cb04f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/SetSmlManagerConfig.fc @@ -0,0 +1,18 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::SetSmlManagerConfig::NAME = "setSmlCfg"u; + +;; field names +const int md::SetSmlManagerConfig::nativeFee = 0; +const int md::SetSmlManagerConfig::zroFee = 1; + +cell md::SetSmlManagerConfig::New(int nativeFee, int zroFee) inline method_id { + return cl::declare( + md::SetSmlManagerConfig::NAME, + unsafeTuple([ + [cl::t::coins, nativeFee], ;; md::SetSmlManagerConfig::nativeFee + [cl::t::coins, zroFee] ;; md::SetSmlManagerConfig::zroFee + ]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/tests/main.fc new file mode 100644 index 00000000..0db89769 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/tests/main.fc @@ -0,0 +1,44 @@ +#include "../LzSend.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../funC++/classlib.fc"; +#include "../MdObj.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "MsgData"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +;;; ===============================TESTS========================================= + +(int, slice) LzSend::fillSendInfo(cell $storage) impure { + cell $lzSend = MOCK_LZ_SEND(); + + cell $manualSet = $lzSend + .cl::set(md::LzSend::sendRequestId, 1) + .cl::set(md::LzSend::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell $helperSet = md::LzSend::fillRequestInfo( + $lzSend, + 1, + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + SEND_MSGLIB_CONNECTION_ADDRESS + ); + + return test::shouldBeTrue( + $manualSet.cl::hash() == $helperSet.cl::hash() + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([LzSend::fillSendInfo, "LzSend::fillSendInfo"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/tests/serde.fc new file mode 100644 index 00000000..19bb7206 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/classes/msgdata/tests/serde.fc @@ -0,0 +1,956 @@ +#include "../AddMsglib.fc"; +#include "../MdAddress.fc"; +#include "../MdObj.fc"; +#include "../LzSend.fc"; +#include "../ChannelNonceInfo.fc"; +#include "../Deploy.fc"; +#include "../ExtendedMd.fc"; +#include "../LzReceivePrepare.fc"; +#include "../LzReceiveStatus.fc"; +#include "../MdEid.fc"; +#include "../MessagingReceipt.fc"; +#include "../MsglibSendCallback.fc"; +#include "../Nonce.fc"; +#include "../OptionsV1.fc"; +#include "../OptionsV2.fc"; +#include "../PacketId.fc"; +#include "../PacketSent.fc"; +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/testutils.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Msgdata Serde"; } + +;; AddMsglib: Has 1 sanitize +(int, slice) Serde::md::AddMsglib::sanitize(cell $unused) impure { + cell $addMsglib = md::AddMsglib::New(SEND_MSGLIB_MANAGER_ADDRESS, DST_OAPP); + + cell $sanitizedAddMsglib = md::AddMsglib::sanitize( + _dupWithGarbage($addMsglib) + ); + + return test::build::equal($addMsglib, $sanitizedAddMsglib); +} + +;; ChannelNonceInfo: Has 1 multi-getter (deserializer) +(int, slice) Serde::md::ChannelNonceInfo::deserialize(cell $unused) impure { + cell $storage = md::ChannelNonceInfo::New(NONCE, FIRST_UNEXECUTED_NONCE); + + (int nonce, int firstUnexecutedNonce) = md::ChannelNonceInfo::deserialize($storage); + + return test::multiget::equal( + $storage, + unsafeTuple([ + md::ChannelNonceInfo::nonce, + md::ChannelNonceInfo::firstUnexecutedNonce + ]), + unsafeTuple([ + nonce, + firstUnexecutedNonce + ]) + ); +} + +;; CoinsAmount: Has 1 sanitize +(int, slice) Serde::md::CoinsAmount::sanitize(cell $unused) impure { + cell $coinsAmount = md::CoinsAmount::New(NATIVE_FEE); + + cell $sanitizedCoinsAmount = md::CoinsAmount::sanitize( + _dupWithGarbage($coinsAmount) + ); + + return test::build::equal($coinsAmount, $sanitizedCoinsAmount); +} + +;; Deploy Has 2 sanitizes +(int, slice) Serde::md::Deploy::sanitize(cell $unused) impure { + cell $deploy = md::Deploy::New(NATIVE_FEE, DST_EID, DST_OAPP); + + cell $sanitizedDeploy = md::Deploy::sanitize( + _dupWithGarbage($deploy) + ); + + return test::build::equal($deploy, $sanitizedDeploy); +} + +(int, slice) Serde::md::Deploy::NewWithExtraInfo::sanitize(cell $unused) impure { + cell $deploy = md::Deploy::NewWithExtraInfo( + NATIVE_FEE, + DST_EID, + DST_OAPP, + begin_cell().store_uint(1, 256).end_cell() + ); + + cell $sanitizedDeploy = md::Deploy::NewWithExtraInfo::sanitize( + _dupWithGarbage($deploy) + ); + + return test::build::equal($deploy, $sanitizedDeploy); +} + +;; ExtendedMd: Has 1 builder, +;; Has 1 getters +;; Has 2 multi-getter (deserializer) +;; Has 1 sanitize +(int, slice) Serde::md::ExtendedMd::build(cell $unused) impure { + cell $expected = md::ExtendedMd::New( + MOCK_LZ_SEND(), + MOCK_EP_CONFIG(true), + DST_OAPP + ); + + return test::build::equal( + $expected, + md::ExtendedMd::build( + MOCK_LZ_SEND(), + MOCK_EP_CONFIG(true), + DST_OAPP + ) + ); +} + +(int, slice) Serde::md::ExtendedMd::getObj(cell $unused) impure { + cell $mdExtended = md::ExtendedMd::New( + MOCK_LZ_SEND(), + MOCK_EP_CONFIG(true), + DST_OAPP + ); + + return test::getRef::equal( + $mdExtended, + md::ExtendedMd::getObj, + md::ExtendedMd::obj + ); +} + +(int, slice) Serde::md::ExtendedMd::getMdAndForwardingAddress(cell $unused) impure { + cell $mdExtended = md::ExtendedMd::New( + MOCK_LZ_SEND(), + MOCK_EP_CONFIG(true), + DST_OAPP + ); + + (cell $md, int forwardingAddress) = md::ExtendedMd::getMdAndForwardingAddress($mdExtended); + + return test::multiget::equal( + $mdExtended, + unsafeTuple([md::ExtendedMd::md, md::ExtendedMd::forwardingAddress]), + unsafeTuple([$md, forwardingAddress]) + ); +} + +(int, slice) Serde::md::ExtendedMd::deserialize(cell $unused) impure { + cell $mdExtended = md::ExtendedMd::New( + MOCK_LZ_SEND(), + MOCK_EP_CONFIG(true), + DST_OAPP + ); + + ( + cell $md, + cell $obj, + int forwardingAddress + ) = md::ExtendedMd::deserialize($mdExtended); + + return test::multiget::equal( + $mdExtended, + unsafeTuple([ + md::ExtendedMd::md, + md::ExtendedMd::obj, + md::ExtendedMd::forwardingAddress + ]), + unsafeTuple([ + $md, + $obj, + forwardingAddress + ]) + ); +} + +(int, slice) Serde::md::ExtendedMd::sanitize(cell $unused) impure { + cell $mdExtended = md::ExtendedMd::New( + MOCK_LZ_SEND(), + MOCK_EP_CONFIG(true), + DST_OAPP + ); + + cell $sanitizedMdExtended = md::ExtendedMd::sanitize( + _dupWithGarbage($mdExtended) + ); + + return test::build::equal($mdExtended, $sanitizedMdExtended); +} + +;; LzReceivePrepare: Has 1 getter +;; Has 1 multi-getter (deserializer) +(int, slice) Serde::md::LzReceivePrepare::getNanotons(cell $unused) impure { + cell $lzReceivePrepare = md::LzReceivePrepare::New(NONCE, RESERVE_NANOS); + + return test::getData::equal( + $lzReceivePrepare, + md::LzReceivePrepare::getNanotons, + md::LzReceivePrepare::nanotons + ); +} + +(int, slice) Serde::md::LzReceivePrepare::deserialize(cell $unused) impure { + cell $lzReceivePrepare = md::LzReceivePrepare::New(NONCE, RESERVE_NANOS); + + ( + int nonce, + int nanotons + ) = md::LzReceivePrepare::deserialize($lzReceivePrepare); + + return test::multiget::equal( + $lzReceivePrepare, + unsafeTuple([ + md::LzReceivePrepare::nonce, + md::LzReceivePrepare::nanotons + ]), + unsafeTuple([ + nonce, + nanotons + ]) + ); +} + +;; LzReceiveStatus: Has 2 builders, +;; Has 1 multi-getter +;; Has 1 sanitize +(int, slice) Serde::md::LzReceiveStatus::build::withFalse(cell $unused) impure { + return test::build::equal( + md::LzReceiveStatus::New(false, NONCE), + md::LzReceiveStatus::build(false, NONCE) + ); +} + +(int, slice) Serde::md::LzReceiveStatus::build::withTrue(cell $unused) impure { + return test::build::equal( + md::LzReceiveStatus::New(true, NONCE), + md::LzReceiveStatus::build(true, NONCE) + ); +} + +(int, slice) Serde::md::LzReceiveStatus::getSuccessAndNonce(cell $unused) impure { + cell $storage = md::LzReceiveStatus::New(false, NONCE); + + (int actualSuccess, int actualNonce) = md::LzReceiveStatus::getSuccessAndNonce($storage); + + return test::multiget::equal( + $storage, + unsafeTuple([md::LzReceiveStatus::success, md::LzReceiveStatus::nonce]), + unsafeTuple([actualSuccess, actualNonce]) + ); +} + +(int, slice) Serde::md::LzReceiveStatus::NewFull::sanitize(cell $unused) impure { + cell $lzReceiveStatus = md::LzReceiveStatus::New(true, NONCE); + + cell $sanitizedLzReceiveStatus = md::LzReceiveStatus::NewFull::sanitize( + _dupWithGarbage($lzReceiveStatus) + ); + + return test::build::equal($lzReceiveStatus, $sanitizedLzReceiveStatus); +} + +;; LzSend: Has 1 builder, +;; Has 3 getters, +;; Has 2 multi-getter (deserializer) +;; Has 2 low-level optimized setters +(int, slice) Serde::md::LzSend::build(cell $unused) impure { + return test::build::equal( + md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + MOCK_NONCELESS_PACKET(), + MOCK_CALLBACK_DATA() + ), + md::LzSend::build( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + MOCK_NONCELESS_PACKET(), + MOCK_CALLBACK_DATA() + ) + ); +} + +(int, slice) Serde::md::LzSend::getSendRequestId(cell $unused) impure { + cell $lzSend = MOCK_LZ_SEND_WITH_ID(5678); + + return test::getData::equal( + $lzSend, + md::LzSend::getSendRequestId, + md::LzSend::sendRequestId + ); +} + +(int, slice) Serde::md::LzSend::getSendMsglib(cell $unused) impure { + cell $lzSend = MOCK_LZ_SEND_WITH_ID(NONCE); + + return test::getData::equal( + $lzSend, + md::LzSend::getSendMsglib, + md::LzSend::sendMsglib + ); +} + +(int, slice) Serde::md::LzSend::getPacket(cell $unused) impure { + cell $lzSend = MOCK_LZ_SEND_WITH_ID(NONCE); + + return test::getRef::equal( + $lzSend, + md::LzSend::getPacket, + md::LzSend::packet + ); +} + +(int, slice) Serde::md::LzSend::getPath(cell $unused) impure { + cell $lzSend = MOCK_LZ_SEND_WITH_ID(NONCE); + + cell $packet = $lzSend.cl::get(md::LzSend::packet); + cell $path = $packet.cl::get(lz::Packet::path); + + return test::build::equal( + $path, + md::LzSend::getPath($lzSend) + ); +} + +(int, slice) Serde::md::LzSend::deserializeSendCallback(cell $unused) impure { + cell $storage = MOCK_LZ_SEND_WITH_ID(NONCE); + + ( + int requestId, + int nativeFee, + int zroFee, + cell $extraOptions, + cell $enforcedOptions, + int sendMsglibManager + ) = md::LzSend::deserializeSendCallback($storage); + + return test::multiget::equal( + $storage, + unsafeTuple([ + md::LzSend::sendRequestId, + md::LzSend::nativeFee, + md::LzSend::zroFee, + md::LzSend::extraOptions, + md::LzSend::enforcedOptions, + md::LzSend::sendMsglibManager + ]), + unsafeTuple([requestId, nativeFee, zroFee, $extraOptions, $enforcedOptions, sendMsglibManager]) + ); +} + +(int, slice) Serde::md::LzSend::getQuoteInformation(cell $unused) impure { + cell $storage = MOCK_LZ_SEND_WITH_ID(NONCE); + + ( + cell $packet, + cell $extraOptions, + cell $enforcedOptions + ) = md::LzSend::getQuoteInformation($storage); + + return test::multiget::equal( + $storage, + unsafeTuple([ + md::LzSend::packet, + md::LzSend::extraOptions, + md::LzSend::enforcedOptions + ]), + unsafeTuple([$packet, $extraOptions, $enforcedOptions]) + ); +} + +(int, slice) Serde::md::LzSend::getPacketAndCallbackData(cell $unused) impure { + cell $lzSend = MOCK_LZ_SEND_WITH_ID(NONCE); + + (cell $packet, cell callbackData) = md::LzSend::getPacketAndCallbackData($lzSend); + + return test::multiget::equal( + $lzSend, + unsafeTuple([md::LzSend::packet, md::LzSend::callbackData]), + unsafeTuple([$packet, callbackData]) + ); +} + +(int, slice) Serde::md::LzSend::fillRequestInfo(cell $unused) impure { + cell $initLzSend = MOCK_LZ_SEND_WITH_ID(NONCE); + + ( + int newRequestId, + int newSendMsglibManager, + int newSendMsglib, + int newSendMsglibConnection + ) = (5501, SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell $expectedLzSend = $initLzSend + .cl::set(md::LzSend::sendRequestId, newRequestId) + .cl::set(md::LzSend::sendMsglibManager, newSendMsglibManager) + .cl::set(md::LzSend::sendMsglib, newSendMsglib) + .cl::set(md::LzSend::sendMsglibConnection, newSendMsglibConnection); + + cell $lzSend = $initLzSend.md::LzSend::fillRequestInfo( + newRequestId, + newSendMsglibManager, + newSendMsglib, + newSendMsglibConnection + ); + + return test::build::equal( + $expectedLzSend, + $lzSend + ); +} + +(int, slice) Serde::md::LzSend::fillRequestInfoWithGarbage(cell $unused) impure { + cell $initLzSend = MOCK_LZ_SEND_WITH_ID(NONCE); + + ( + int newRequestId, + int newSendMsglibManager, + int newSendMsglib, + int newSendMsglibConnection + ) = (5501, SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell $expectedLzSend = $initLzSend + .cl::set(md::LzSend::sendRequestId, newRequestId) + .cl::set(md::LzSend::sendMsglibManager, newSendMsglibManager) + .cl::set(md::LzSend::sendMsglib, newSendMsglib) + .cl::set(md::LzSend::sendMsglibConnection, newSendMsglibConnection); + + cell $garbageInitLzSend = begin_cell() + .store_uint("garbage"u, 256) + .store_slice($initLzSend.begin_parse().sskipfirst(256, 0)) + .end_cell(); + + cell $lzSend = $garbageInitLzSend.md::LzSend::fillRequestInfo( + newRequestId, + newSendMsglibManager, + newSendMsglib, + newSendMsglibConnection + ); + + return test::build::equal( + $expectedLzSend, + $lzSend + ); +} + +(int, slice) Serde::md::LzSend::setPacketNonceAndGuid(cell $unused) impure { + cell $initLzSend = MOCK_LZ_SEND_WITH_ID(NONCE); + + int packetNonce = 12387; + int packetGuid = 543208; + + cell $expectedLzSend = $initLzSend.cl::set( + md::LzSend::packet, + $initLzSend.cl::get(md::LzSend::packet) + .cl::set(lz::Packet::nonce, packetNonce) + .cl::set(lz::Packet::guid, packetGuid) + ); + + cell $optimizedLzSend = $initLzSend.md::LzSend::setPacketNonceAndGuid(packetNonce, packetGuid); + + return test::set::equal($expectedLzSend, $optimizedLzSend); +} + +;; MdAddress: Has 1 builder, +;; Has 1 getter, +;; Has 1 multi-getter (deserializer) +;; Has 1 sanitize +(int, slice) Serde::md::MdAddress::build(cell $unused) impure { + return test::build::equal( + md::MdAddress::New(MOCK_LZ_SEND(), DST_OAPP), + md::MdAddress::build(MOCK_LZ_SEND(), DST_OAPP) + ); +} + +(int, slice) Serde::md::MdAddress::getMd(cell $unused) impure { + cell $storage = md::MdAddress::New(MOCK_LZ_SEND(), DST_OAPP); + + return test::getRef::equal( + $storage, + md::MdAddress::getMd, + md::MdAddress::md + ); +} + +(int, slice) Serde::md::MdAddress::deserialize(cell $unused) impure { + cell $mdAddress = md::MdAddress::New(MOCK_LZ_SEND(), DST_OAPP); + + cell expectedMd = $mdAddress.cl::get(md::MdAddress::md); + int expectedAddress = $mdAddress.cl::get
(md::MdAddress::address); + + (cell md, int address) = md::MdAddress::deserialize($mdAddress); + + return test::multiget::equal( + $mdAddress, + unsafeTuple([md::MdAddress::md, md::MdAddress::address]), + unsafeTuple([md, address]) + ); +} + +(int, slice) Serde::md::MdAddress::sanitize(cell $unused) impure { + cell $mdAddress = md::MdAddress::New(MOCK_LZ_SEND(), DST_OAPP); + + cell $sanitizedMdAddress = md::MdAddress::sanitize( + _dupWithGarbage($mdAddress) + ); + + return test::build::equal($mdAddress, $sanitizedMdAddress); +} + +;; MdEid: Has 1 sanitize +(int, slice) Serde::md::MdEid::sanitize(cell $unused) impure { + cell $mdEid = md::MdEid::New(MOCK_LZ_SEND(), DST_EID); + + cell $sanitizedMdEid = md::MdEid::sanitize( + _dupWithGarbage($mdEid) + ); + + return test::build::equal($mdEid, $sanitizedMdEid); +} + +;; MdObj: Has 1 builder, +;; Has 2 getters, +;; Has 1 multi-getter (deserializer) +;; Has 1 sanitize + +(int, slice) Serde::md::MdObj::build(cell $unused) impure { + return test::build::equal( + md::MdObj::New(MOCK_LZ_SEND(), MOCK_ADMIN_WORKER_LIST()), + md::MdObj::build(MOCK_LZ_SEND(), MOCK_ADMIN_WORKER_LIST()) + ); +} + +(int, slice) Serde::md::MdObj::getMd(cell $unused) impure { + cell $storage = md::MdObj::New(MOCK_LZ_SEND(), MOCK_ADMIN_WORKER_LIST()); + + return test::getRef::equal( + $storage, + md::MdObj::getMd, + md::MdObj::md + ); +} + +(int, slice) Serde::md::MdObj::getObj(cell $unused) impure { + cell $storage = md::MdObj::New(MOCK_LZ_SEND(), MOCK_ADMIN_WORKER_LIST()); + + return test::getRef::equal( + $storage, + md::MdObj::getObj, + md::MdObj::obj + ); +} + +(int, slice) Serde::md::MdObj::deserialize(cell $unused) impure { + cell $mdObj = md::MdObj::New(MOCK_LZ_SEND(), MOCK_ADMIN_WORKER_LIST()); + + cell expectedMd = $mdObj.cl::get(md::MdObj::md); + cell expectedObj = $mdObj.cl::get(md::MdObj::obj); + + (cell md, cell obj) = md::MdObj::deserialize($mdObj); + + return test::multiget::equal( + $mdObj, + unsafeTuple([md::MdObj::md, md::MdObj::obj]), + unsafeTuple([expectedMd, expectedObj]) + ); +} + +(int, slice) Serde::md::MdObj::sanitize(cell $unused) impure { + cell $mdObj = md::MdObj::New(MOCK_LZ_SEND(), MOCK_ADMIN_WORKER_LIST()); + + cell $sanitizedMdObj = md::MdObj::sanitize( + _dupWithGarbage($mdObj) + ); + + return test::build::equal($mdObj, $sanitizedMdObj); +} + +;; MessagingReceipt: Has 1 builder, +;; Has 1 multi-getter (deserializer) +(int, slice) Serde::md::MessagingReceipt::build(cell $unused) impure { + return test::build::equal( + md::MessagingReceipt::New(MOCK_LZ_SEND(), NATIVE_FEE, ZRO_FEE, 5), + md::MessagingReceipt::build(MOCK_LZ_SEND(), NATIVE_FEE, ZRO_FEE, 5) + ); +} + +(int, slice) Serde::md::MessagingReceipt::getErrorCodeAndLzSend(cell $unused) impure { + cell $messagingReceipt = md::MessagingReceipt::New(MOCK_LZ_SEND(), NATIVE_FEE, ZRO_FEE, 0); + + int expectedErrorCode = $messagingReceipt.cl::get(md::MessagingReceipt::errorCode); + cell $expectedLzSend = $messagingReceipt.cl::get(md::MessagingReceipt::lzSend); + + ( + int actualErrorCode, + cell actualLzSend + ) = md::MessagingReceipt::getErrorCodeAndLzSend($messagingReceipt); + + return test::multiget::equal( + $messagingReceipt, + unsafeTuple([md::MessagingReceipt::errorCode, md::MessagingReceipt::lzSend]), + unsafeTuple([actualErrorCode, actualLzSend]) + ); +} + +;; MsglibSendCallback: Has 1 builder, +;; Has 1 getter, +;; Has 1 multi-getter (deserializer) +(int, slice) Serde::md::MsglibSendCallback::build(cell $unused) impure { + return test::build::equal( + md::MsglibSendCallback::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_LZ_SEND(), + MOCK_SEND_PACKET(), + serializePayees(MOCK_PAYEES(3)), + NONCE_BYTE_OFFSET, + NONCE_BYTES, + GUID_BYTE_OFFSET, + GUID_BYTES, + lz::SmlJobAssigned::New(MOCK_FEE), + Channel::NO_ERROR + ), + md::MsglibSendCallback::build( + NATIVE_FEE, + ZRO_FEE, + MOCK_LZ_SEND(), + MOCK_SEND_PACKET(), + serializePayees(MOCK_PAYEES(3)), + NONCE_BYTE_OFFSET, + NONCE_BYTES, + GUID_BYTE_OFFSET, + GUID_BYTES, + lz::SmlJobAssigned::New(MOCK_FEE), + Channel::NO_ERROR + ) + ); +} + +(int, slice) Serde::md::MsglibSendCallback::getLzSend(cell $unused) impure { + cell $msglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_PAYEES( + MOCK_LZ_SEND(), + MOCK_PAYEES(3) + ); + + return test::getRef::equal( + $msglibSendCallback, + md::MsglibSendCallback::getLzSend, + md::MsglibSendCallback::lzSend + ); +} + +(int, slice) Serde::md::MsglibSendCallback::deserialize(cell $unused) impure { + cell $msglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_PAYEES( + MOCK_LZ_SEND(), + MOCK_PAYEES(3) + ); + + ( + int errorCode, + int nativeFee, + int zroFee, + cell $lzSend, + cell $payees, + cell encodedPacket, + int nonceByteOffset, + int nonceBytes, + int guidByteOffset, + int guidBytes, + cell $msglibSendEvents + ) = md::MsglibSendCallback::deserialize($msglibSendCallback); + + return test::multiget::equal( + $msglibSendCallback, + unsafeTuple([ + md::MsglibSendCallback::errorCode, + md::MsglibSendCallback::nativeFee, + md::MsglibSendCallback::zroFee, + md::MsglibSendCallback::lzSend, + md::MsglibSendCallback::payees, + md::MsglibSendCallback::packetEncoded, + md::MsglibSendCallback::nonceByteOffset, + md::MsglibSendCallback::nonceBytes, + md::MsglibSendCallback::guidByteOffset, + md::MsglibSendCallback::guidBytes, + md::MsglibSendCallback::msglibSendEvents + ]), + unsafeTuple([ + errorCode, + nativeFee, + zroFee, + $lzSend, + $payees, + encodedPacket, + nonceByteOffset, + nonceBytes, + guidByteOffset, + guidBytes, + $msglibSendEvents + ]) + ); +} + +;; Nonce: Has 1 builder, +;; Has 1 getter, +;; Has 1 sanitize +(int, slice) Serde::md::Nonce::build(cell $unused) impure { + cell $storage = md::Nonce::New(NONCE + 87); + + return test::build::equal( + $storage, + md::Nonce::build(NONCE + 87) + ); +} + +(int, slice) Serde::md::Nonce::getNonce(cell $unused) impure { + cell $storage = md::Nonce::New(NONCE + 54); + + return test::getData::equal( + $storage, + md::Nonce::getNonce, + md::Nonce::nonce + ); +} + +(int, slice) Serde::md::Nonce::sanitize(cell $unused) impure { + cell $nonce = md::Nonce::New(NONCE + 54); + + cell $sanitizedNonce = md::Nonce::sanitize( + _dupWithGarbage($nonce) + ); + + return test::build::equal($nonce, $sanitizedNonce); +} + +;; OptionsV1: Has 1 getter, +;; Has 1 multi-getter (deserializer) +(int, slice) Serde::md::OptionsV1::getLzReceiveGas(cell $unused) impure { + cell $options = MOCK_EXTRA_OPTIONS_V1(); + + return test::getData::equal( + $options, + md::OptionsV1::getLzReceiveGas, + md::OptionsV1::lzReceiveGas + ); +} + +(int, slice) Serde::md::OptionsV1::decodeCoins(cell $unused) impure { + cell $options = MOCK_EXTRA_OPTIONS_V1(); + + ( + int lzReceiveGas, + int lzReceiveValue, + int nativeDropAmount + ) = md::OptionsV1::decodeCoins($options); + + return test::multiget::equal( + $options, + unsafeTuple([ + md::OptionsV1::lzReceiveGas, + md::OptionsV1::lzReceiveValue, + md::OptionsV1::nativeDropAmount + ]), + unsafeTuple([lzReceiveGas, lzReceiveValue, nativeDropAmount]) + ); +} + +;; OptionsV2: Has 1 multi-getter (deserializer) +(int, slice) Serde::md::OptionsV2::decodeCoins(cell $unused) impure { + cell $options = MOCK_EXTRA_OPTIONS_V2(); + + ( + int lzComposeGas, + int lzComposeValue, + int lzReceiveGas, + int lzReceiveValue, + int nativeDropAmount + ) = md::OptionsV2::decodeCoins($options); + + return test::multiget::equal( + $options, + unsafeTuple([ + md::OptionsV2::lzComposeGas, + md::OptionsV2::lzComposeValue, + md::OptionsV2::lzReceiveGas, + md::OptionsV2::lzReceiveValue, + md::OptionsV2::nativeDropAmount + ]), + unsafeTuple([lzComposeGas, lzComposeValue, lzReceiveGas, lzReceiveValue, nativeDropAmount]) + ); +} + +;; OptionsExtended: Has 1 Multi-getter (deserializer) +(int, slice) Serde::md::OptionsExtended::deserialize(cell $unused) impure { + cell $optionsExtended = MOCK_OPTIONS_EXTENDED(); + + (int eid, int msgType, cell $options) = md::OptionsExtended::deserialize($optionsExtended); + + return test::multiget::equal( + $optionsExtended, + unsafeTuple([md::OptionsExtended::eid, md::OptionsExtended::msgType, md::OptionsExtended::options]), + unsafeTuple([eid, msgType, $options]) + ); +} + +;; PacketSent: Has 1 builder +(int, slice) Serde::md::PacketSent::build(cell $unused) impure { + return test::build::equal( + md::PacketSent::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_ENFORCED_OPTIONS_V1(), + MOCK_EXTRA_OPTIONS_V2(), + BytesEncoder::build(MOCK_SEND_PACKET()).BytesEncoder::serialize(), + NONCE, + SEND_MSGLIB_ADDRESS, + lz::SmlJobAssigned::New(MOCK_FEE) + ), + md::PacketSent::build( + NATIVE_FEE, + ZRO_FEE, + MOCK_ENFORCED_OPTIONS_V1(), + MOCK_EXTRA_OPTIONS_V2(), + BytesEncoder::build(MOCK_SEND_PACKET()).BytesEncoder::serialize(), + NONCE, + SEND_MSGLIB_ADDRESS, + lz::SmlJobAssigned::New(MOCK_FEE) + ) + ); +} + +;; PacketId: Has 1 builder +;; Has 1 sanitize +(int, slice) Serde::md::PacketId::build(cell $unused) impure { + return test::build::equal( + md::PacketId::New(MOCK_SEND_PATH(), NONCE), + md::PacketId::build(MOCK_SEND_PATH(), NONCE) + ); +} + +(int, slice) Serde::md::PacketId::sanitize(cell $unused) impure { + cell $packetId = md::PacketId::New(MOCK_SEND_PATH(), NONCE); + + cell $sanitizedPacketId = md::PacketId::sanitize( + _dupWithGarbage($packetId) + ); + + return test::build::equal($packetId, $sanitizedPacketId); +} + +;; SetAddress: Has 1 sanitize +(int, slice) Serde::md::SetAddress::sanitize(cell $unused) impure { + cell $setAddress = md::SetAddress::New(DST_OAPP); + + cell $sanitizedSetAddress = md::SetAddress::sanitize( + _dupWithGarbage($setAddress) + ); + + return test::build::equal($setAddress, $sanitizedSetAddress); +} + + +;; SetEpConfig: Has 1 sanitize +(int, slice) Serde::md::SetEpConfig::sanitize(cell $unused) impure { + cell $setEpConfig = MOCK_SET_EP_CONFIG_MD(true); + + cell $sanitizedSetEpConfig = md::SetEpConfig::sanitize( + _dupWithGarbage($setEpConfig) + ); + + return test::build::equal($setEpConfig, $sanitizedSetEpConfig); +} + +;;; ===============================TESTS========================================= +tuple baseTest::getTests() impure { + return empty_tuple() + ;; -- AddMsgLib + .tpush([Serde::md::AddMsglib::sanitize, "Serde::md::AddMsglib::sanitize"]) + ;; -- ChannelNonceInfo + .tpush([Serde::md::ChannelNonceInfo::deserialize, "Serde::md::ChannelNonceInfo::deserialize"]) + ;; -- CoinsAmount + .tpush([Serde::md::CoinsAmount::sanitize, "Serde::md::CoinsAmount::sanitize"]) + ;; -- Deploy + .tpush([Serde::md::Deploy::sanitize, "Serde::md::Deploy::sanitize"]) + .tpush([Serde::md::Deploy::NewWithExtraInfo::sanitize, "Serde::md::Deploy::NewWithExtraInfo::sanitize"]) + ;; -- ExtendedMd + .tpush([Serde::md::ExtendedMd::build, "Serde::md::ExtendedMd::build"]) + .tpush([Serde::md::ExtendedMd::getObj, "Serde::md::ExtendedMd::getObj"]) + .tpush([Serde::md::ExtendedMd::getMdAndForwardingAddress, "Serde::md::ExtendedMd::getMdAndForwardingAddress"]) + .tpush([Serde::md::ExtendedMd::deserialize, "Serde::md::ExtendedMd::deserialize"]) + .tpush([Serde::md::ExtendedMd::sanitize, "Serde::md::ExtendedMd::sanitize"]) + ;; -- LzReceivePrepare + .tpush([Serde::md::LzReceivePrepare::getNanotons, "Serde::md::LzReceivePrepare::getNanotons"]) + .tpush([Serde::md::LzReceivePrepare::deserialize, "Serde::md::LzReceivePrepare::deserialize"]) + ;; -- LzReceiveStatus + .tpush([Serde::md::LzReceiveStatus::build::withFalse, "Serde::md::LzReceiveStatus::build::withFalse"]) + .tpush([Serde::md::LzReceiveStatus::build::withTrue, "Serde::md::LzReceiveStatus::build::withTrue"]) + .tpush([Serde::md::LzReceiveStatus::getSuccessAndNonce, "Serde::md::LzReceiveStatus::getSuccessAndNonce"]) + .tpush([Serde::md::LzReceiveStatus::NewFull::sanitize, "Serde::md::LzReceiveStatus::NewFull::sanitize"]) + ;; -- LzSend + .tpush([Serde::md::LzSend::build, "Serde::md::LzSend::build"]) + .tpush([Serde::md::LzSend::getSendRequestId, "Serde::md::LzSend::getSendRequestId"]) + .tpush([Serde::md::LzSend::getPacket, "Serde::md::LzSend::getPacket"]) + .tpush([Serde::md::LzSend::getSendMsglib, "Serde::md::LzSend::getSendMsglib"]) + .tpush([Serde::md::LzSend::getPath, "Serde::md::LzSend::getPath"]) + .tpush([Serde::md::LzSend::deserializeSendCallback, "Serde::md::LzSend::deserializeSendCallback"]) + .tpush([Serde::md::LzSend::getPacketAndCallbackData, "Serde::md::LzSend::getPacketAndCallbackData"]) + .tpush([Serde::md::LzSend::getQuoteInformation, "Serde::md::LzSend::getQuoteInformation"]) + .tpush([Serde::md::LzSend::fillRequestInfo, "Serde::md::LzSend::fillRequestInfo"]) + .tpush([Serde::md::LzSend::fillRequestInfoWithGarbage, "Serde::md::LzSend::fillRequestInfoWithGarbage"]) + .tpush([Serde::md::LzSend::setPacketNonceAndGuid, "Serde::md::LzSend::setPacketNonceAndGuid"]) + ;; -- MdAddress + .tpush([Serde::md::MdAddress::build, "Serde::md::MdAddress::build"]) + .tpush([Serde::md::MdAddress::getMd, "Serde::md::MdAddress::getMd"]) + .tpush([Serde::md::MdAddress::deserialize, "Serde::md::MdAddress::deserialize"]) + .tpush([Serde::md::MdAddress::sanitize, "Serde::md::MdAddress::sanitize"]) + ;; -- MdEid + .tpush([Serde::md::MdEid::sanitize, "Serde::md::MdEid::sanitize"]) + ;; -- MdObj + .tpush([Serde::md::MdObj::build, "Serde::md::MdObj::build"]) + .tpush([Serde::md::MdObj::getMd, "Serde::md::MdObj::getMd"]) + .tpush([Serde::md::MdObj::getObj, "Serde::md::MdObj::getObj"]) + .tpush([Serde::md::MdObj::deserialize, "Serde::md::MdObj::deserialize"]) + .tpush([Serde::md::MdObj::sanitize, "Serde::md::MdObj::sanitize"]) + ;; -- MessagingReceipt + .tpush([Serde::md::MessagingReceipt::build, "Serde::md::MessagingReceipt::build"]) + .tpush([Serde::md::MessagingReceipt::getErrorCodeAndLzSend, "Serde::md::MessagingReceipt::getErrorCodeAndLzSend"]) + ;; -- MsglibSendCallback + .tpush([Serde::md::MsglibSendCallback::build, "Serde::md::MsglibSendCallback::build"]) + .tpush([Serde::md::MsglibSendCallback::getLzSend, "Serde::md::MsglibSendCallback::getLzSend"]) + .tpush([Serde::md::MsglibSendCallback::deserialize, "Serde::md::MsglibSendCallback::deserialize"]) + ;; -- Nonce + .tpush([Serde::md::Nonce::build, "Serde::md::Nonce::build"]) + .tpush([Serde::md::Nonce::getNonce, "Serde::md::Nonce::getNonce"]) + .tpush([Serde::md::Nonce::sanitize, "Serde::md::Nonce::sanitize"]) + ;; -- OptionsV1 + .tpush([Serde::md::OptionsV1::getLzReceiveGas, "Serde::md::OptionsV1::getLzReceiveGas"]) + .tpush([Serde::md::OptionsV1::decodeCoins, "Serde::md::OptionsV1::decodeCoins"]) + ;; -- OptionsV2 + .tpush([Serde::md::OptionsV2::decodeCoins, "Serde::md::OptionsV2::decodeCoins"]) + ;; -- OptionsExtended + .tpush([Serde::md::OptionsExtended::deserialize, "Serde::md::OptionsExtended::deserialize"]) + ;; -- PacketSent + .tpush([Serde::md::PacketSent::build, "Serde::md::PacketSent::build"]) + ;; -- PacketId + .tpush([Serde::md::PacketId::build, "Serde::md::PacketId::build"]) + .tpush([Serde::md::PacketId::sanitize, "Serde::md::PacketId::sanitize"]) + ;; -- SetAddress + .tpush([Serde::md::SetAddress::sanitize, "Serde::md::SetAddress::sanitize"]) + ;; -- SetEpConfig + .tpush([Serde::md::SetEpConfig::sanitize, "Serde::md::SetEpConfig::sanitize"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/abstract/contractMainAbstract.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/abstract/contractMainAbstract.fc new file mode 100644 index 00000000..9d0b02b6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/abstract/contractMainAbstract.fc @@ -0,0 +1,14 @@ +;; _executeOpcode is executed at the start of contractMain, after checking permissions. +;; Each contract implements a list of opcodes, which calls a handler as the entry point +;; of that opcode. The contract must be initialized in order to start executing opcodes. +;; @in the opcode to execute +;; @in the message data extracted from txnContext +;; @out tuple of resultant actions +tuple _executeOpcode(int op, cell $md) impure inline; + +;; Actions are executed after the opcode is executed. Each opcode must emit a tuple +;; of actions, where each action is executed after the opcode is executed in order. +;; @in the action to execute +;; @in the value of the action +;; @out a bool that determines if excess balance is to be sent back to the origin. +int _executeAction(int actionType, tuple action) impure inline; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/abstract/handlerAbstract.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/abstract/handlerAbstract.fc new file mode 100644 index 00000000..df5b88dc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/abstract/handlerAbstract.fc @@ -0,0 +1,20 @@ +;; Assert that the owner has sent an INITIALIZE opcode to this contract +() assertInitialized() impure inline; + +;; Assert the identity of the caller for a given opcode +() checkPermissions(int op, cell $md) impure inline; + +;; Initialize this contract +tuple initialize(cell $md) impure inline; + +;; Authentication = the owner has deployed and sent at least one transaction to this contract +;; unauthenticated contracts cannot perform any actions. +;; Authentication enables sharded contracts to trust the identity and intention of other shards +() authenticateIfNecessary() impure inline; + +;; Assert the caller is the owner of this contract +() assertOwner() impure inline; + +;; Asserts permissions of caller for permissioned opcodes before executing opcode +;; @see contractMainAbstract.fc +() _checkPermissions(int op, cell $md) impure inline; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/RawCall.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/RawCall.fc new file mode 100644 index 00000000..ce926d2f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/RawCall.fc @@ -0,0 +1,43 @@ +#include "utils.fc"; +#include "../utils.fc"; + +const int action::rawCall::NAME = "rawCall"u; + +const int action::rawCall::to = 1; +const int action::rawCall::body = 2; + +;; Call a method on the contract at address `to` with the given message data `md` +;; optionally provide value provisioned from this contract's balance +;; @terminal +tuple action::rawCall::create(int to, cell body) impure inline { + return unsafeTuple([action::rawCall::NAME, to, body]); +} + +;; returns true if equals +int action::rawCall::equals(tuple self, tuple other) impure inline { + return ( + (self.int_at(0) == other.int_at(0)) ;; NAME + & (self.int_at(action::rawCall::to) == other.int_at(action::rawCall::to)) + & (self.cell_at(action::rawCall::body).cell_hash() == other.cell_at(action::rawCall::body).cell_hash()) + ); +} + +;; overloaded when you want to pass 0 outflowNanos +tuple _newAction(int to, cell body) impure inline { + return action::rawCall::create(to, body); +} + +;; overloaded when you want to pass 0 outflowNanos +(tuple, ()) ~pushAction(tuple actions, int to, cell body) impure inline { + return (actions.tpush(_newAction(to, body)), ()); +} + +int executeRawCall(tuple rawCallAction) impure inline { + sendTerminalAction( + rawCallAction.int_at(action::rawCall::to), + rawCallAction.cell_at(action::rawCall::body), + null(), + NORMAL + ); + return false; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/call.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/call.fc new file mode 100644 index 00000000..21d0eb7d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/call.fc @@ -0,0 +1,54 @@ +#include "utils.fc"; +#include "../classlib.fc"; + +const int action::call::NAME = "actionCall"u; + +const int action::call::to = 1; +const int action::call::opcode = 2; +const int action::call::md = 3; + +;; Call a method on the contract at address `to` with the given message data `md` +;; optionally provide value provisioned from this contract's balance +;; @terminal +tuple action::call::create(int to, int opcode, cell $md) impure inline { + return unsafeTuple([action::call::NAME, to, opcode, $md]); +} + +;; returns true if equals +int action::call::equals(tuple self, tuple other) impure inline { + int equalMdField = compareObjectFields(self.cell_at(action::call::md), other.cell_at(action::call::md)); + if (equalMdField != -1) { + ~strdump("call: not equal md field at idx "); + ~dump(equalMdField); + } + return ( + (self.int_at(0) == other.int_at(0)) ;; NAME + & (self.int_at(action::call::to) == other.int_at(action::call::to)) + & (self.int_at(action::call::opcode) == other.int_at(action::call::opcode)) + & (equalMdField == -1) + ); +} + +;; overloaded when you want to pass 0 outflowNanos +tuple _newAction(int to, int opcode, cell $body) impure inline { + return action::call::create(to, opcode, $body); +} + +;; overloaded when you want to pass 0 outflowNanos +(tuple, ()) ~pushAction(tuple actions, int to, int opcode, cell $body) impure inline { + return (actions.tpush(_newAction(to, opcode, $body)), ()); +} + +int executeCall(tuple callAction) impure inline { + sendTerminalAction( + callAction.int_at(action::call::to), + buildLayerzeroMessageBody( + 0, + callAction.int_at(action::call::opcode), + callAction.cell_at(action::call::md) + ), + null(), + NORMAL + ); + return false; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/deploy.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/deploy.fc new file mode 100644 index 00000000..1ccb0608 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/deploy.fc @@ -0,0 +1,117 @@ +#include "utils.fc"; + +;;; ======================================================= +;; Deploys a contract defined by the given code and storage object, +;; and calls the contract with the given message data. +;; Typical usage will be to deploy and initialize +;;; ======================================================= +const int action::deploy::NAME = "deploy"u; + +const int action::deploy::code = 1; +const int action::deploy::storage = 2; +const int action::deploy::donationNanos = 3; +const int action::deploy::opcode = 4; +const int action::deploy::md = 5; +;; @info reserve donationNanos nanoton as the deployed contract's rent + value +;; @info in addition to the message value, use from_balance nanoton +;; from the contract's balance to pay for the deploy +;; @info e.g., if from_balance == outflowNanos, the entire rent is paid from the deployer +;; contract's balance +const int action::deploy::outflowNanos = 6; + +;; @terminal +tuple action::deploy::create( + cell code, + cell $storage, + int donationNanos, + int opcode, + cell $md, + int outflowNanos +) impure inline { + return unsafeTuple( + [action::deploy::NAME, code, $storage, donationNanos, opcode, $md, outflowNanos] + ); +} + +int action::deploy::equals(tuple self, tuple other) impure { + int equalDataField = compareObjectFields( + self.cell_at(action::deploy::storage), + other.cell_at(action::deploy::storage) + ); + int equalMdField = compareObjectFields( + self.cell_at(action::deploy::md), + other.cell_at(action::deploy::md) + ); + return ( + (self.int_at(0) == other.int_at(0)) ;; NAME + & (self.cell_at( action::deploy::code ).cell_hash() == other.cell_at( action::deploy::code ).cell_hash()) + & (equalDataField == -1) + & (self.int_at(action::deploy::donationNanos) == other.int_at(action::deploy::donationNanos)) + & (self.int_at(action::deploy::opcode) == other.int_at(action::deploy::opcode)) + & (equalMdField == -1) + & (self.int_at(action::deploy::outflowNanos) == other.int_at(action::deploy::outflowNanos)) + ); +} + +tuple _newAction( + cell code, + cell $storage, + int donationNanos, + int opcode, + cell $md, + int outflowNanos +) impure inline { + return action::deploy::create( + code, + $storage, + donationNanos, + opcode, + $md, + outflowNanos + ); +} + +(tuple, ()) ~pushAction( + tuple actions, + cell code, + cell $storage, + int donationNanos, + int opcode, + cell $md, + int outflowNanos +) impure inline { + return ( + actions + .tpush(_newAction( + code, + $storage, + donationNanos, + opcode, + $md, + outflowNanos + )), + () + ); +} + +int executeDeploy(tuple action) impure inline { + cell $storageObj = action.cell_at(action::deploy::storage); + cell codeCell = action.cell_at(action::deploy::code); + + sendTerminalAction( + computeContractAddress($storageObj, codeCell), + buildLayerzeroMessageBody( + action.int_at(action::deploy::donationNanos), + action.int_at(action::deploy::opcode), + action.cell_at(action::deploy::md) + ), + begin_cell() + .store_uint(6, 5) + .store_ref(codeCell) + .store_ref($storageObj) + .end_cell(), + NORMAL + ); + + return false; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/dispatch.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/dispatch.fc new file mode 100644 index 00000000..ece0138b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/dispatch.fc @@ -0,0 +1,60 @@ +#include "utils.fc"; + +#include "call.fc"; + +const int action::dispatch::NAME = "dispatch"u; + +const int action::dispatch::to = 1; +const int action::dispatch::opcode = 2; +const int action::dispatch::md = 3; +const int action::dispatch::gasNanos = 4; + +;; Call a method on the contract at address `to` with the given message data `md` +;; optionally provide value provisioned from this contract's balance +;; @terminal +tuple action::dispatch::create(int to, int opcode, cell $md, int gasNanos) inline { + return unsafeTuple([action::dispatch::NAME, to, opcode, $md, gasNanos]); +} + +;; returns true if equals +int action::dispatch::equals(tuple self, tuple other) { + int equalMdField = compareObjectFields( + self.cell_at(action::dispatch::md), + other.cell_at(action::dispatch::md) + ); + return ( + (self.int_at(0) == other.int_at(0 )) ;; NAME + & (self.int_at(action::dispatch::to) == other.int_at(action::dispatch::to)) + & (self.int_at(action::dispatch::opcode) == other.int_at(action::dispatch::opcode)) + & (equalMdField == -1) + & (self.int_at(action::dispatch::gasNanos) == other.int_at(action::dispatch::gasNanos)) + ); +} + +tuple _newAction(int to, int opcode, cell $body, int gasNanos) inline { + return action::dispatch::create(to, opcode, $body, gasNanos); +} + +;; overloaded when you want to pass 0 outflowNanos +(tuple, ()) ~pushAction(tuple actions, int to, int opcode, cell $body, int gasNanos) inline { + return (actions.tpush(_newAction(to, opcode, $body, gasNanos)), ()); +} + +int executeDispatch(tuple dispatchAction) { + cell body = buildLayerzeroMessageBody( + 0, + dispatchAction.int_at(action::call::opcode), + dispatchAction.cell_at(action::call::md) + ); + + (int cellsCount, int bitsCount, _) = body.compute_data_size(MAX_U16); + + sendNonTerminalAction( + SEND_MSG_BOUNCEABLE, + dispatchAction.int_at(action::dispatch::gasNanos) + get_forward_fee(cellsCount, bitsCount, false), + dispatchAction.int_at(action::call::to), + body, + NORMAL + ); + return true; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/event.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/event.fc new file mode 100644 index 00000000..d49b492e --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/event.fc @@ -0,0 +1,89 @@ +#include "../baseInterface.fc"; +#include "../classlib.fc"; +#include "utils.fc"; + +const int action::event::NAME = "event"u; + +const int action::event::bodyIndex = 1; + +const int action::event::topic = 0; +const int action::event::body = 1; +const int action::event::initialStorage = 2; + +;; Interface function you must implement to get the event sink +int _getEventSink() impure inline; + +;; @info Events in LZ contracts are internal messages to an event sink +;; where the resulting transaction always reverts +;; @non-terminal +cell action::event::New(int topic, cell $body, cell $initialStorage) impure inline method_id { + return cl::declare( + action::event::NAME, + unsafeTuple([ + [cl::t::uint256, topic], + [cl::t::objRef, $body], + [cl::t::objRef, $initialStorage] + ]) + ); +} + +const int action::event::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 3); +const int action::event::_headerFillerBits = _HEADER_WIDTH - action::event::_headerInfoBits; +const int action::event::_headerInfo = 7850279558805522911016931325; + +cell action::event::build(int topic, cell $body, cell $initialStorage) impure inline method_id { + return begin_cell() + .store_uint(action::event::_headerInfo, action::event::_headerInfoBits) ;; header info + .store_ones(action::event::_headerFillerBits) ;; header filler + .store_uint256(topic) + .store_ref($body) + .store_ref($initialStorage) + .end_cell(); +} + +tuple action::event::create(int topic, cell $body, cell $initialStorage) impure inline { + return unsafeTuple([ + action::event::NAME, + action::event::build(topic, $body, $initialStorage) + ]); +} + +;; returns true if equals +int action::event::equals(tuple self, tuple other) impure { + int equalEventObj = compareObjectFields( + self.cell_at(action::event::bodyIndex), + other.cell_at(action::event::bodyIndex) + ); + + return ( + (self.int_at(0) == other.int_at(0)) ;; NAME + & (equalEventObj == -1) + ); +} + +;; interface this function because it requires passing the 'getInitialStorage' from the baseHandler +;; tuple _newAction(int topic, cell $body) impure inline { +;; return action::event::create(topic, $body, getInitialStorage()); +;; } + +tuple _newAction(int topic, cell $body) impure inline; + +(tuple, ()) ~pushAction(tuple actions, int topic, cell $body) impure inline { + return (actions.tpush(_newAction(topic, $body)), ()); +} + +int executeEvent(tuple action) impure inline { + ;; send event to event sink + sendNonTerminalAction( + SEND_MSG_NON_BOUNCEABLE, + 0, + _getEventSink(), + buildLayerzeroMessageBody( + 0, + BaseInterface::OP::EVENT, + action.cell_at(action::event::bodyIndex) + ), + PAID_EXTERNALLY + ); + return true; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/payment.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/payment.fc new file mode 100644 index 00000000..5ecb1b1f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/payment.fc @@ -0,0 +1,52 @@ +#include "utils.fc"; + +const int action::payment::NAME = "payment"u; + +;; @info the address to pay +const int action::payment::toAddress = 1; +;; @info the amount to pay, in nanotons +const int action::payment::amount = 2; +;; @info the amount of value to provision from this contract's balance +const int action::payment::outflowNanos = 3; + +;; @non-terminal +tuple action::payment::create(int toAddress, int amount, int outflowNanos) impure inline { + return unsafeTuple([action::payment::NAME, toAddress, amount, outflowNanos]); +} + +;; returns true if equals +int action::payment::equals(tuple self, tuple other) impure { + return ( + (self.int_at(0) == other.int_at(0)) ;; NAME + & (self.int_at(action::payment::toAddress) == other.int_at(action::payment::toAddress)) + & (self.int_at(action::payment::amount) == other.int_at(action::payment::amount)) + & (self.int_at(action::payment::outflowNanos) == other.int_at(action::payment::outflowNanos)) + ); +} + +tuple _newAction(int toAddress, int amount, int outflowNanos) impure inline { + return action::payment::create(toAddress, amount, outflowNanos); +} + +(tuple, ()) ~pushAction(tuple actions, int toAddress, int amount, int outflowNanos) impure inline { + if (outflowNanos > 0) { + actions = actions + .tset(ACTIONS_OUTFLOW, actions.int_at(ACTIONS_OUTFLOW) + outflowNanos); + } + return (actions.tpush(_newAction(toAddress, amount, outflowNanos)), ()); +} + +int executePayment(tuple action) impure inline { + sendNonTerminalAction( + SEND_MSG_NON_BOUNCEABLE, + get_forward_fee(BASECHAIN, 0, 1) + + action.int_at(action::payment::amount), + action.int_at(action::payment::toAddress), + empty_cell(), + NORMAL + ); + + ;; this being true means we're assuming there's going to + ;; be some value left over after the payment is made + return true; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/sendJettons.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/sendJettons.fc new file mode 100644 index 00000000..ec41154f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/sendJettons.fc @@ -0,0 +1,75 @@ +#include "utils.fc"; + +const int action::sendJettons::NAME = "sendJetton"u; + +;; @info the wallet of this contract's jetton wallet +const int action::sendJettons::walletAddress = 1; +;; @info the address to pay +const int action::sendJettons::toAddress = 2; + +;; @info the amount to pay +const int action::sendJettons::amount = 3; + +const int action::sendJettons::responseAddress = 4; + +;; Op codes: +;; This because we cannot import it directly, this is effectively +;; const op::transfer_notification = 0x7362d09c; +;; const op::transfer = 0xf8a7ea5; +const int Jetton::OP::TRANSFER_NOTIFICATION = 0x7362d09c; +const int Jetton::OP::TRANSFER = 0xf8a7ea5; + +;; @terminal +tuple action::sendJettons::create(int walletAddress, int toAddress, int amount, int responseAddress) impure inline { + return unsafeTuple([action::sendJettons::NAME, walletAddress, toAddress, amount, responseAddress]); +} + +;; returns true if equals +int action::sendJettons::equals(tuple self, tuple other) impure { + return ( + (self.int_at(0) == other.int_at(0)) ;; NAME + & (self.int_at(action::sendJettons::walletAddress) == other.int_at(action::sendJettons::walletAddress)) + & (self.int_at(action::sendJettons::toAddress) == other.int_at(action::sendJettons::toAddress)) + & (self.int_at(action::sendJettons::amount) == other.int_at(action::sendJettons::amount)) + & (self.int_at(action::sendJettons::responseAddress) == other.int_at(action::sendJettons::responseAddress)) + ); +} + +tuple _newAction(int walletAddress, int toAddress, int amount, int responseAddress) impure inline { + return action::sendJettons::create(walletAddress, toAddress, amount, responseAddress); +} + +(tuple, ()) ~pushAction( + tuple actions, + int walletAddress, + int toAddress, + int amount, + int responseAddress +) impure inline { + return (actions.tpush(_newAction(walletAddress, toAddress, amount, responseAddress)), ()); +} + +cell buildJettonMessageBody(int toAddress, int amount, int responseAddress) impure inline { + return beginTonMessage(Jetton::OP::TRANSFER) ;; opcode, query_id + .store_coins(amount) ;; jetton amount + .store_slice(hashpartToBasechainAddressStd(toAddress)) ;; to address address + .store_slice(hashpartToBasechainAddressStd(responseAddress)) ;; response address + .store_maybe_ref(null()) ;; custom_payload + .store_coins(0) ;; forward_ton_amount + .store_maybe_ref(null()) ;; either_forward_payload is stored as a maybe_ref + .end_cell(); +} + +int executeSendJettons(tuple dispatchAction) impure { + sendTerminalAction( + dispatchAction.int_at(action::sendJettons::walletAddress), + buildJettonMessageBody( + dispatchAction.int_at(action::sendJettons::toAddress), + dispatchAction.int_at(action::sendJettons::amount), + dispatchAction.int_at(action::sendJettons::responseAddress) + ), + null(), + NORMAL + ); + return false; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/utils.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/utils.fc new file mode 100644 index 00000000..9e2595cc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/actions/utils.fc @@ -0,0 +1,47 @@ +#include "../txnContext.fc"; + +;; Small file for now, but a placeholder for generic actions utility functions + +const int ACTIONS_OUTFLOW = 0; + +tuple emptyActions() inline { + return unsafeTuple([0]); +} + +;;; ====================================================================================== +;; @info terminal actions are always sent using all non-reserved balance on the contract +() sendTerminalAction(int toAddress, cell messageBody, cell stateInit, int extraFlags) impure inline { + builder b = begin_cell() + .store_uint(SEND_MSG_BOUNCEABLE, 6) + .store_slice(hashpartToBasechainAddressStd(toAddress)) + .store_coins(0); + b = stateInit.is_null() + ? b.store_uint(1, 107) + : b.store_uint(7, 108).store_ref(stateInit); + send_raw_message(b.store_ref(messageBody).end_cell(), CARRY_ALL_BALANCE | extraFlags); +} + +;; @info non-terminal actions must specify the amount of funds to send +() sendNonTerminalAction(int bounceable, int amount, int toAddress, cell messageBody, int extraFlags) impure inline { + cell msg = begin_cell() + .store_uint(bounceable, 6) + .store_slice(hashpartToBasechainAddressStd(toAddress)) + .store_coins(amount) + .store_uint(1, 107) + .store_ref(messageBody) + .end_cell(); + send_raw_message(msg, extraFlags); +} +;; @param donationNanos: the amount of TON that the sender intended to be +;; withheld within our contract +;; @info baseHandler::refund_addr is the last known "origin" of a message +;; flow, and is used to refund the sender if the handler does not +;; use all remaining value from the in_message +cell buildLayerzeroMessageBody(int donationNanos, int opcode, cell $md) impure inline { + cell ret = beginTonMessage(opcode) + .store_coins(donationNanos) + .store_slice(getOriginStd()) + .store_ref($md) + .end_cell(); + return ret; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/baseInterface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/baseInterface.fc new file mode 100644 index 00000000..b14a5622 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/baseInterface.fc @@ -0,0 +1,15 @@ +const int BaseInterface::event::AUTHENTICATED = "AUTHENTICATED"u; +const int BaseInterface::event::INITIALIZED = "INITIALIZED"u; + +const int BaseInterface::ERROR::notAuthenticated = 257; +const int BaseInterface::ERROR::onlyOwner = 258; +const int BaseInterface::ERROR::notInitialized = 259; +const int BaseInterface::ERROR::alreadyInitialized = 260; +const int BaseInterface::ERROR::invalidOpcode = 261; +const int BaseInterface::ERROR::eventEmitted = 262; +const int BaseInterface::ERROR::invalidActionType = 263; +const int BaseInterface::ERROR::invalidEventSource = 264; + +const int BaseInterface::OP::INITIALIZE = "BaseInterface::OP::INITIALIZE"c; +const int BaseInterface::OP::EMPTY = 0; +const int BaseInterface::OP::EVENT = "BaseInterface::OP::EVENT"c; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/classlib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/classlib.fc new file mode 100644 index 00000000..d8c27989 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/classlib.fc @@ -0,0 +1,867 @@ +#include "utils.fc"; +#include "stringlib.fc"; + +;; Types for storage abstraction +const int cl::t::bool = 0; +const int cl::t::uint8 = 3; +const int cl::t::uint16 = 4; +const int cl::t::uint32 = 5; +const int cl::t::uint64 = 6; +const int cl::t::coins = 7; ;; fixed-width uint128 +const int cl::t::uint256 = 8; +const int cl::t::address = cl::t::uint256; +const int cl::t::cellRef = 9; +const int cl::t::dict256 = cl::t::cellRef; +const int cl::t::objRef = cl::t::cellRef; +const int cl::t::addressList = cl::t::cellRef; + +const int DICT256_KEYLEN = 256; + +const int cl::NULL_CLASS_NAME = "NULL"u; + +const int cl::ERROR::INVALID_CLASS = 1057; +const int cl::ERROR::MALFORMED_OBJECT = 1058; + + +const int MAX_NAME_LEN = 10; ;; each name can be up to 10 characters long +const int _NAME_WIDTH = 8 * MAX_NAME_LEN; ;; convert to bits +const int _BASIC_HEADER_WIDTH = _NAME_WIDTH; +const int MAX_NAME_INTLEN = (1 << (8 * MAX_NAME_LEN)) - 1; + +const int _FIELD_TYPE_WIDTH+_CELL_ID_WIDTH = 6; ;; support up to 16 types + +const int _FIELD_TYPE_WIDTH = 4; ;; support up to 16 types +const int _CELL_ID_WIDTH = 2; ;; the classlib backend supports up to 4 inner cells including root +const int _DATA_OFFSET_WIDTH = 10; ;; 1023 bits per cell = 2**10 - 1 +const int _REF_OFFSET_WIDTH = 2; ;; each cell can have up to 4 refs +const int _FIELD_INFO_WIDTH = _FIELD_TYPE_WIDTH + _CELL_ID_WIDTH + _DATA_OFFSET_WIDTH + _REF_OFFSET_WIDTH; +const int _MAX_CLASS_FIELDS = 15; ;; reserve 0xff for the "invalid" object field name +const int INVALID_CLASS_MEMBER = 15; +const int _HEADER_WIDTH = _BASIC_HEADER_WIDTH + _MAX_CLASS_FIELDS * _FIELD_INFO_WIDTH; + +;; declarations require a tuple of the form [[ type, val ], ...] +const int FIELD_TYPE_IDX = 0; +const int FIELD_VAL_IDX = 1; + +;;; ====================== Class functions ====================== +;; returns type width in bits +int _getTypeWidth(int clType) impure inline { + if (clType <= cl::t::uint256) { + return 1 << clType; ;; type names are set up so this is true + } + ;; all other types are ref types with 0 data bits + return 0; +} + +int cl::hash(cell $obj) impure inline { + return $obj.cell_hash(); +} + +int cl::isNullObject(cell $obj) impure inline { + return $obj.cell_is_empty(); +} + +;; checks if a class lib object is flat, and contains no 'refs' +;; null is considered 'flat' +int cl::noRefFields(cell $obj) impure { + slice headerSlice = $obj.begin_parse(); + int numRefs = headerSlice.slice_refs(); + + if (numRefs == 0) { + return true; + } elseif (numRefs <= 2) { + ;; if there are refs, the struct is not flat + return false; + } + + if (numRefs >= 3) { + if ( + (headerSlice.preload_ref_at(0).cell_is_empty() == false) + | (headerSlice.preload_ref_at(1).cell_is_empty() == false) + | (headerSlice.preload_ref_at(2).begin_parse().slice_refs() != 0) + ) { + ;; if there is 1 structural node, that structural node must have 0 refs + return false; + } + } + + if (numRefs >= 4) { + if (headerSlice.preload_ref_at(3).begin_parse().slice_refs() != 0) { + return false; + } + } + + return true; +} + +int cl::equalObjTypeShallow(cell $a, cell $b) impure { + slice aSlice = $a.begin_parse(); + slice bSlice = $b.begin_parse(); + int aRefs = aSlice.slice_refs(); + + if ( + (aRefs != bSlice.slice_refs()) + | (aSlice.slice_bits() != bSlice.slice_bits()) + ) { + return false; + } + + int refIndex = 2; + while (refIndex < aRefs) { + if ( + (aSlice.preload_ref_at(refIndex).begin_parse().slice_refs() != bSlice.preload_ref_at(refIndex).begin_parse().slice_refs()) + | (aSlice.preload_ref_at(refIndex).begin_parse().slice_bits() != bSlice.preload_ref_at(refIndex).begin_parse().slice_bits()) + ) { + return false; + } + refIndex += 1; + } + + return true; +} + +int cl::typeof(cell $obj) impure inline method_id { + if (cl::isNullObject($obj)) { + return cl::NULL_CLASS_NAME; + } + return $obj.begin_parse().preload_uint(_NAME_WIDTH); +} + +cell cl::declare(int name, tuple fields) impure inline { + ;; Initialize a tuple with [null, empty_builder] to store cell builders + tuple classBuilder = unsafeTuple([null(), begin_cell()]); + + ;; Get number of fields of the object we want to create + int num_fields = fields.tlen(); + ;; Start building header with class name + builder headerBuilder = begin_cell().store_uint(name, _NAME_WIDTH); + + ;; Initialize tracking variables + int curDataCell = 1; ;; Current cell for storing data fields + int curRefCell = 1; ;; Current cell for storing reference fields + ;; root node is special as it only allows two ref fields + int curCellMaxRefs = 2; ;; Max references allowed in current cell + int curDataOffset = _HEADER_WIDTH; ;; Current bit offset in data cell + int curRefOffset = 0; ;; Current reference offset in ref cell + + ;; Iterate through all fields + int curField = 0; + while (curField < num_fields) { + ;; Get current field and its type from tuple + tuple field = fields.tuple_at(curField); + int fieldType = field.int_at(FIELD_TYPE_IDX); + + ;; Get number of bits needed for this field type + ;; (2^{bitLength} for uints, 0 for Refs) + int fieldBits = _getTypeWidth(fieldType); + + if (fieldBits > 0) { + ;; If adding this integer field would exceed cell bit limit + if ((curDataOffset + fieldBits) > MAX_CELL_BITS) { + curDataCell += 1; ;; Move to next cell + curDataOffset = 0; ;; Reset bit offset + ;; Add new cell builder if needed + if (curDataCell >= classBuilder.tlen()) { + classBuilder = classBuilder.tpush(begin_cell()); + } + } + } else { + ;; For reference types (fieldBits == 0) + ;; If adding this ref would exceed cell ref limit + if ((curRefOffset + 1) > curCellMaxRefs) { + curRefCell += 1; ;; Move to next cell + curRefOffset = 0; ;; Reset ref offset + curCellMaxRefs = MAX_CELL_REFS; ;; Use max refs for non-root cells + ;; Add new cell builder if needed + if (curRefCell >= classBuilder.tlen()) { + classBuilder = classBuilder.tpush(begin_cell()); + } + } + } + + ;; Store field value based on type + if (fieldType <= cl::t::address) { + ;; For numeric types, store as uint + classBuilder = classBuilder.tset( + curDataCell, + cast_to_builder(classBuilder.at(curDataCell)) + .store_uint(abs(field.int_at(FIELD_VAL_IDX)), fieldBits) + ); + } elseif (fieldType == cl::t::objRef) { + ;; For object references, store as ref + classBuilder = classBuilder.tset( + curRefCell, + cast_to_builder(classBuilder.at(curRefCell)) + .store_ref(field.cell_at(FIELD_VAL_IDX)) + ); + } else { + throw(CLASSLIB::ERROR::INVALID_FIELD_TYPE); + } + + ;; Build field metadata in header + headerBuilder = headerBuilder + .store_uint(fieldType, _FIELD_TYPE_WIDTH); + if (fieldBits > 0) { + ;; For data fields, store cell index, bit offset, and ref offset + headerBuilder = headerBuilder + .store_uint(curDataCell == 1 ? 0 : curDataCell, _CELL_ID_WIDTH) + .store_uint(curDataOffset, _DATA_OFFSET_WIDTH) + .store_uint(3, _REF_OFFSET_WIDTH); + curDataOffset += fieldBits; + } else { + ;; For ref fields, store cell index and ref offset + headerBuilder = headerBuilder + .store_uint(curRefCell == 1 ? 0 : curRefCell, _CELL_ID_WIDTH) + .store_uint(MAX_CELL_BITS, _DATA_OFFSET_WIDTH) + .store_uint(curRefOffset, _REF_OFFSET_WIDTH); + curRefOffset += 1; + } + curField += 1; + } + + ;; Get root cell builder and count total cells + builder rootBuilder = classBuilder.at(1); + int numCells = classBuilder.tlen() - 1; + + ;; For multi-cell objects, ensure root has exactly 2 refs + if (numCells > 1) { + if (rootBuilder.builder_refs() == 0) { + rootBuilder = rootBuilder + .store_ref(empty_cell()) + .store_ref(empty_cell()); + } elseif (rootBuilder.builder_refs() == 1) { + rootBuilder = rootBuilder + .store_ref(empty_cell()); + } + } + + ;; Finalize header and combine with root cell + headerBuilder = headerBuilder + .store_ones(_HEADER_WIDTH - headerBuilder.builder_bits()) + .store_builder(rootBuilder); + + ;; Return final cell based on number of cells used + if (numCells == 1) { + return headerBuilder.end_cell(); + } + if (numCells == 2) { + return headerBuilder + .store_ref(classBuilder.at(2).end_cell()) + .end_cell(); + } + return headerBuilder + .store_ref(classBuilder.at(2).end_cell()) + .store_ref(classBuilder.at(3).end_cell()) + .end_cell(); +} + +cell cl::nullObject() impure inline method_id { + return empty_cell(); +} + +;;; ====================== Class Setter ====================== +int cl::getFieldType::asm(slice self, int fieldInfoOffset) asm """ +// STACK: left -> right: bottom -> top // +// Setup // STACK [ headerSlice, fieldInfoOffset ] +4 PUSHINT // STACK [ headerSlice, fieldInfoOffset, _FIELD_TYPE_WIDTH ] +SDSUBSTR // STACK [ substring ] +4 PLDU // STACK [ 2BitUnsignInt ] +"""; + +int cl::getFieldCellIndex::asm(slice self, int fieldInfoOffset) asm """ +// STACK: left -> right: bottom -> top // +// Setup // STACK [ headerSlice, fieldInfoOffset ] +4 ADDCONST // STACK [ headerSlice, fieldInfoOffset + _FIELD_TYPE_WIDTH ] +2 PUSHINT // STACK [ headerSlice, fieldInfoOffset + _FIELD_TYPE_WIDTH, _CELL_ID_WIDTH ] +SDSUBSTR // STACK [ substring ] +2 PLDU // STACK [ 2BitUnsignInt ] +"""; + +int cl::getFieldOffset::asm(slice self, int fieldInfoOffset) asm """ +// STACK: left -> right: bottom -> top // +// Setup // STACK [ headerSlice, fieldInfoOffset ] +6 ADDCONST // STACK [ headerSlice, fieldInfoOffset + _FIELD_TYPE_WIDTH + _CELL_ID_WIDTH ] +10 PUSHINT // STACK [ headerSlice, fieldInfoOffset + _FIELD_TYPE_WIDTH + _CELL_ID_WIDTH, _DATA_OFFSET_WIDTH ] +SDSUBSTR // STACK [ substring ] +10 PLDU // STACK [ 10BitUnsignInt ] +"""; + +int cl::getFieldCellOffset::asm(slice self, int fieldInfoOffset) asm """ +// STACK: left -> right: bottom -> top // +// Setup // STACK [ headerSlice, fieldInfoOffset ] +16 ADDCONST // STACK [ headerSlice, fieldInfoOffset + _FIELD_TYPE_WIDTH + _CELL_ID_WIDTH + _DATA_OFFSET_WIDTH ] +2 PUSHINT // STACK [ headerSlice, fieldInfoOffset + _FIELD_TYPE_WIDTH + _CELL_ID_WIDTH + _DATA_OFFSET_WIDTH, _REF_OFFSET_WIDTH ] +SDSUBSTR // STACK [ substring ] +2 PLDU // STACK [ 10BitUnsignInt ] +"""; + +int cl::preload_bits_offset_3::asm(int width1, slice self, int fieldOffset, int width2) asm """ +// STACK: left -> right: bottom -> top // +// Setup // STACK [ width1, headerSlice, fieldOffset, width2 ] +SDSUBSTR // STACK [ width1, substring ] +s1 XCHG0 // STACK [ substring, width1 ] +PLDUX // STACK [ 10BitUnsignInt ] ( CC + 1 ) +"""; + +forall X -> cell cl::set(cell $self, int fieldName, X val) impure inline method_id { + slice headerSlice = $self.begin_parse(); + int fieldInfoOffset = _BASIC_HEADER_WIDTH + (fieldName * _FIELD_INFO_WIDTH); + int fieldCellIndex = headerSlice.cl::getFieldCellIndex::asm(fieldInfoOffset); + int fieldType = headerSlice.cl::getFieldType::asm(fieldInfoOffset); + int fieldOffset = headerSlice.cl::getFieldOffset::asm(fieldInfoOffset); + int fieldRefsOffset = headerSlice.cl::getFieldCellOffset::asm(fieldInfoOffset); + + int fieldWidth = _getTypeWidth(fieldType); + + slice victim = fieldCellIndex == 0 + ? headerSlice + : headerSlice.preload_ref_at(fieldCellIndex).begin_parse(); + if (fieldWidth != 0) { + fieldRefsOffset = MAX_CELL_REFS; + } + + builder replacement = begin_cell() + .store_slice( + victim.scutfirst( + min(victim.slice_bits(), fieldOffset), + min(fieldRefsOffset, victim.slice_refs()) + ) + ); + + if (fieldType == cl::t::cellRef) { + replacement = replacement + .store_ref(val.cast_to_cell()) + .store_slice(victim.scutlast(0, victim.slice_refs() - fieldRefsOffset - 1)); + } else { + ;; numeric type + replacement = replacement + .store_uint(abs(val.cast_to_int()), fieldWidth) + .store_slice(victim.sskipfirst(fieldOffset + fieldWidth, victim.slice_refs())); + } + + if (fieldCellIndex > 0) { + ;; link the replacement into the root cell + return begin_cell() + .store_slice(headerSlice.scutfirst(headerSlice.slice_bits(), fieldCellIndex)) + .store_ref(replacement.end_cell()) + .store_slice(headerSlice.scutlast(0, headerSlice.slice_refs() - fieldCellIndex - 1)) + .end_cell(); + } + return replacement.end_cell(); +} +;;; ====================== Class Getters ====================== + +const int _NAME_WIDTH = 8 * MAX_NAME_LEN; ;; convert to bits +const int _BASIC_HEADER_WIDTH = _NAME_WIDTH; +const int MAX_NAME_INTLEN = (1 << (8 * MAX_NAME_LEN)) - 1; + +const int _FIELD_TYPE_WIDTH = 4; ;; support up to 16 types +const int _CELL_ID_WIDTH = 2; ;; the classlib backend supports up to 4 inner cells including root +const int _DATA_OFFSET_WIDTH = 10; ;; 1023 bits per cell = 2**10 - 1 + + +int cl::get(cell $self, int fieldName, int width) impure inline method_id { + slice headerSlice = $self.begin_parse(); + + int fieldInfoOffset = _BASIC_HEADER_WIDTH + (fieldName * _FIELD_INFO_WIDTH); + int fieldCellIndex = headerSlice.cl::getFieldCellIndex::asm(fieldInfoOffset); + int fieldOffset = headerSlice.cl::getFieldOffset::asm(fieldInfoOffset); + + if (fieldCellIndex == 0) { + return cl::preload_bits_offset_3::asm(width, headerSlice, fieldOffset, width); + } else { + return cl::preload_bits_offset_3::asm(width, headerSlice.preload_ref_at(fieldCellIndex).begin_parse(), fieldOffset, width); + } +} + +cell cl::get(cell $self, int fieldName) impure inline method_id { + slice headerSlice = $self.begin_parse(); + int fieldInfoOffset = _BASIC_HEADER_WIDTH + (fieldName * _FIELD_INFO_WIDTH); + int fieldCellIndex = headerSlice.cl::getFieldCellIndex::asm(fieldInfoOffset); + int fieldRefIdx = headerSlice.cl::getFieldCellOffset::asm(fieldInfoOffset); + + if (fieldCellIndex == 0) { + return headerSlice.preload_ref_at(fieldRefIdx); + } + + return headerSlice + .preload_ref_at(fieldCellIndex) + .begin_parse() + .preload_ref_at(fieldRefIdx) + ; +} + +cell cl::get(cell $self, int fieldName) impure inline method_id { + return cl::get($self, fieldName); +} + +int cl::get(cell $self, int fieldName) impure inline method_id { + return $self.cl::get(fieldName, 8); +} + +int cl::get(cell $self, int fieldName) impure inline method_id { + return $self.cl::get(fieldName, 16); +} + +int cl::get(cell $self, int fieldName) impure inline method_id { + return $self.cl::get(fieldName, 32); +} + +int cl::get(cell $self, int fieldName) impure inline method_id { + return $self.cl::get(fieldName, 64); +} + +int cl::get(cell $self, int fieldName) impure inline method_id { + return $self.cl::get(fieldName, 128); +} + +int cl::get(cell $self, int fieldName) impure inline method_id { + return $self.cl::get(fieldName, 256); +} + +slice cl::get(cell $self, int fieldName) impure inline method_id { + return hashpartToBasechainAddressStd( + $self.cl::get(fieldName, 256) + ); +} + +int cl::get(cell $self, int fieldName) impure inline method_id { + return $self.cl::get(fieldName, 1) != 0; +} + +cell cl::get(cell $self, int fieldName) impure inline method_id { + return $self.cl::get(fieldName); +} + +int cl::get
(cell $self, int fieldName) impure inline method_id { + return $self.cl::get(fieldName, 256); +} + +;;; =============== DEBUG / CONVENIENCE FUNCTIONS ================= +int typeofField(cell $self, int fieldName) impure inline { + slice headerSlice = $self.begin_parse(); + int fieldInfoOffset = _BASIC_HEADER_WIDTH + (fieldName * _FIELD_INFO_WIDTH); + return headerSlice + .preload_bits_offset( + fieldInfoOffset, + _FIELD_TYPE_WIDTH + ) + .preload_uint(_FIELD_TYPE_WIDTH); +} + +;; returns -1 (true) if equal, otherwise the index of the first field that differs +;; returns 16 if the types of the objects are not equal +int compareObjectFields(cell $lhs, cell $rhs) impure inline { + int malformed = cl::typeof($lhs) != cl::typeof($rhs); + if (malformed) { + return INVALID_CLASS_MEMBER; + } + if (cl::typeof($lhs) == cl::NULL_CLASS_NAME) { + return -1; + } + int fieldIndex = 0; + while (fieldIndex < INVALID_CLASS_MEMBER) { + int curFieldType = $lhs.typeofField(fieldIndex); + if (curFieldType == cl::t::cellRef) { + malformed = $lhs.cl::get(fieldIndex).cl::hash() != $rhs.cl::get(fieldIndex).cl::hash(); + if (malformed) { + ~dump($lhs.cl::get(fieldIndex).cell_hash()); + ~dump($rhs.cl::get(fieldIndex).cell_hash()); + } + } elseif (curFieldType <= cl::t::uint256) { + int cur_field_width = _getTypeWidth(curFieldType); + malformed = $lhs.cl::get(fieldIndex, cur_field_width) != $rhs.cl::get(fieldIndex, cur_field_width); + if (malformed) { + str::console::log("lhs: ", $lhs.cl::get(fieldIndex, cur_field_width)); + str::console::log("rhs: ", $rhs.cl::get(fieldIndex, cur_field_width)); + } + } else { + ;; Finished iteration + return -1; + } + if (malformed) { + ~strdump("Malformed field"); + ~dump(fieldIndex); + return fieldIndex; + } + fieldIndex += 1; + } + return -1; +} + +int objectsAreEqual(cell $lhs, cell $rhs) impure inline { + return compareObjectFields($lhs, $rhs) == -1; +} + +slice _typeToStr(int fieldType) impure { + if (fieldType == cl::t::uint8) { return "uint8"; } + elseif (fieldType == cl::t::uint16) { return "uint16"; } + elseif (fieldType == cl::t::uint32) { return "uint32"; } + elseif (fieldType == cl::t::uint64) { return "uint64"; } + elseif (fieldType == cl::t::uint256) { return "uint256"; } + elseif (fieldType == cl::t::coins) { return "coins"; } + elseif (fieldType == cl::t::address) { return "address"; } + elseif (fieldType == cl::t::dict256) { return "dict256"; } + elseif (fieldType == cl::t::objRef) { return "objRef"; } + elseif (fieldType == cl::t::cellRef) { return "cellRef"; } + elseif (fieldType == cl::t::bool) { return "bool"; } + else { return "unknown"; } +} + +() printField(cell $obj, int fieldName) impure inline { + slice headerSlice = $obj.begin_parse(); + int fieldType = typeofField($obj, fieldName); + int fieldInfoOffset = _BASIC_HEADER_WIDTH + (fieldName * _FIELD_INFO_WIDTH); + int fieldCellIndex = headerSlice + .preload_bits_offset( + fieldInfoOffset + _FIELD_TYPE_WIDTH, + _CELL_ID_WIDTH + ) + .preload_uint(_CELL_ID_WIDTH); + int fieldRefIdx = headerSlice + .preload_bits_offset( + fieldInfoOffset + _FIELD_TYPE_WIDTH + _CELL_ID_WIDTH + _DATA_OFFSET_WIDTH, + _REF_OFFSET_WIDTH + ) + .preload_uint(_REF_OFFSET_WIDTH); + + int fieldBits = _getTypeWidth(fieldType); + int fieldOffset = headerSlice + .preload_bits_offset( + fieldInfoOffset + _FIELD_TYPE_WIDTH + _CELL_ID_WIDTH, + _DATA_OFFSET_WIDTH + ) + .preload_uint(_DATA_OFFSET_WIDTH); + + slice toPrint = _typeToStr(fieldType) + .str::concat(" ") + .str::concatInt(fieldName) + .str::concat(" at c") + .str::concatInt(fieldCellIndex); + if (fieldBits > 0) { + toPrint = toPrint.str::concat(".b").str::concatInt(fieldOffset); + } else { + toPrint = toPrint.str::concat(".r").str::concatInt(fieldRefIdx); + } + if (fieldType <= cl::t::uint256) { + ~strdump( + toPrint + .str::concat(" = ") + .str::concatInt($obj.cl::get(fieldName, fieldBits)) + ); + } elseif (fieldType == cl::t::objRef) { + ~strdump(toPrint + .str::concat(" hash = ") + .str::concatInt($obj.cl::get(fieldName).cl::hash()) + ); + } else { + ~strdump(toPrint + .str::concat(" hash = ") + .str::concatInt($obj.cl::get(fieldName).cell_hash()) + ); + } +} + +;; doesn't actually return a tuple, just pushes something to the stack casted to a tuple +tuple getObjectField(cell $storage, int field) impure { + int fieldType = typeofField($storage, field); + int fieldBits = _getTypeWidth(fieldType); + if (fieldType == cl::t::bool) { + return unsafeTuple($storage.cl::get(field)); + } elseif (fieldType <= cl::t::uint256) { + return unsafeTuple($storage.cl::get(field, fieldBits)); + } + return unsafeTuple($storage.cl::get(field)); +} + +;; doesn't actually return a tuple, just pushes something to the stack casted to a tuple +tuple getContractStorageField(int field) impure method_id { + return getObjectField(getContractStorage(), field); +} + +;; doesn't actually return a tuple, just pushes something to the stack casted to a tuple +tuple getContractStorageNestedField(int field, int nestedField) impure method_id { + return getObjectField(cast_to_cell(getContractStorageField(field)), nestedField); +} + +;;; ====================== Dictionary functions ====================== + +slice uint256ToSlice(int val) impure inline { + return begin_cell().store_uint256(val).as_slice(); +} + +int sliceToUint256(slice s) impure inline { + return s.preload_uint(256); +} + +;; into a single bit +cell cl::dict256::New() impure inline { + return empty_cell(); +} + +(slice, int) cl::dict256::get(cell dict, int key) impure inline method_id { + if (dict.cell_is_empty()) { + return (null(), false); + } + return dict.udict_get?(DICT256_KEYLEN, key); +} + +(int, int) cl::dict256::get(cell dict, int key) impure inline method_id { + (slice val, int exists) = cl::dict256::get(dict, key); + if (exists) { + return (sliceToUint256(val), true); + } + return (0, false); +} + +(cell, int) cl::dict256::get(cell dict, int key) impure inline method_id { + if (dict.cell_is_empty()) { + return (null(), false); + } + (cell ret, int exists) = dict.udict_get_ref?(DICT256_KEYLEN, key); + ifnot (exists) { + return (null(), false); + } + return (ret, true); +} + +cell cl::dict256::setRef(cell dict, int key, cell val) impure inline method_id { + if (dict.cell_is_empty()) { + return new_dict().udict_set_ref( + DICT256_KEYLEN, + key, + val.cast_to_cell() + ); + } + return dict.udict_set_ref(DICT256_KEYLEN, key, val.cast_to_cell()); +} + +forall X -> cell cl::dict256::set(cell dict, int key, X val) impure inline { + slice _val = val.is_slice() ? val.cast_to_slice() : uint256ToSlice(val.cast_to_int()); + if (dict.cell_is_empty()) { + return new_dict().udict_set(DICT256_KEYLEN, key, _val); + } + return dict.udict_set(DICT256_KEYLEN, key, _val); +} + +cell cl::dict256::delete(cell dict, int key) impure { + if (dict.cell_is_empty()) { + return dict; + } + (cell modified_dict, _) = dict.udict_delete?(DICT256_KEYLEN, key); + return modified_dict.is_cell() ? modified_dict : cl::dict256::New(); +} + +;;; ====================== Dictionary Iterators ====================== +;; returns key, val, and key == -1 if there is no next (or min) element +;; if the val exists, it is returned +;; if a val does not exist, null() is returned + +(int, slice) cl::dict256::getMin(cell dict256) impure inline { + if (dict256.cell_is_empty()) { + return (-1, null()); + } + (int key, slice val, int exists) = dict256.udict_get_min?(DICT256_KEYLEN); + if (exists) { + return (key, val); + } + return (-1, null()); +} + +(int, int) cl::dict256::getMin(cell dict256) impure inline { + if (dict256.cell_is_empty()) { + return (-1, null()); + } + (int key, slice val, int exists) = dict256.udict_get_min?(DICT256_KEYLEN); + if (exists) { + return (key, val.preload_uint(256)); + } + return (-1, null()); +} + +(int, cell) cl::dict256::getMin(cell dict256) impure inline { + if (dict256.cell_is_empty()) { + return (-1, null()); + } + (int key, cell val, int exists) = dict256.udict_get_min_ref?(DICT256_KEYLEN); + if (exists) { + return (key, val); + } + return (-1, null()); +} + +(int, slice) cl::dict256::getNext(cell dict256, int pivot) impure inline { + if (dict256.cell_is_empty()) { + return (-1, null()); + } + (int key, slice val, int exists) = dict256.udict_get_next?(DICT256_KEYLEN, pivot); + if (exists) { + return (key, val); + } + return (-1, null()); +} + +(int, int) cl::dict256::getNext(cell dict256, int pivot) impure inline { + if (dict256.cell_is_empty()) { + return (-1, null()); + } + (int key, slice val, int exists) = dict256.udict_get_next?(DICT256_KEYLEN, pivot); + if (exists) { + return (key, val.preload_uint(256)); + } + return (-1, null()); +} + +(int, cell) cl::dict256::getNext(cell dict256, int pivot) impure inline { + if (dict256.cell_is_empty()) { + return (-1, null()); + } + (int key, slice val, int exists) = dict256.udict_get_next?(DICT256_KEYLEN, pivot); + if (exists) { + return (key, val.preload_first_ref()); + } + return (-1, null()); +} + +int cl::dict256::size(cell dict) impure inline method_id { + int count = 0; + (int pivot, _) = dict.cl::dict256::getMin(); + while (pivot >= 0) { + (pivot, _) = dict.cl::dict256::getNext(pivot); + count = count + 1; + } + return count; +} + +;;; ====================== Nested Dict Helpers ====================== + +forall X -> cell cl::nestedDict256::set(cell $self, int fieldName, int key, X val) impure inline { + return $self.cl::set( + fieldName, + $self + .cl::get(fieldName) + .cl::dict256::set(key, val) + ); +} + +cell cl::nestedDict256::setRef(cell $self, int fieldName, int key, cell val) impure inline { + return $self.cl::set( + fieldName, + $self.cl::get(fieldName).cl::dict256::setRef(key, val) + ); +} + +cell cl::nestedDict256::delete(cell $self, int fieldName, int key) impure inline { + return $self.cl::set( + fieldName, + $self.cl::get(fieldName).cl::dict256::delete(key) + ); +} + +(int, int) cl::nestedDict256::get(cell $self, int fieldName, int key) impure inline { + return $self.cl::get(fieldName).cl::dict256::get(key); +} + +(slice, int) cl::nestedDict256::get(cell $self, int fieldName, int key) impure inline { + return $self.cl::get(fieldName).cl::dict256::get(key); +} + +(cell, int) cl::nestedDict256::get(cell $self, int fieldName, int key) impure inline { + (slice s, int exists) = $self.cl::get(fieldName).cl::dict256::get(key); + if (exists) { + return (s.preload_first_ref(), true); + } + return (null(), false); +} + +;; ========================= Storage View Functions ========================= + +;; -- Level 0: returns storage.fieldName +int getStorageFieldL0(int fieldName) impure method_id { + cell $storage = getContractStorage(); + int fieldType = typeofField($storage, fieldName); + int typeWidth = _getTypeWidth(fieldType); + return cl::get($storage, fieldName, typeWidth); +} + +cell getStorageFieldL0(int fieldName) impure method_id { + return cl::get( + getContractStorage(), + fieldName + ); +} + +cell getStorageFieldL0(int fieldName) impure method_id { + return cl::get( + getContractStorage(), + fieldName + ); +} + +;; -- Level 1: returns storage.fieldName.nestedFieldName +int getStorageFieldL1(int fieldName, int nestedFieldName) impure method_id { + cell field = getStorageFieldL0(fieldName); + int nestedFieldType = typeofField(field, nestedFieldName); + int nestedFieldWidth = _getTypeWidth(nestedFieldType); + return cl::get(field, nestedFieldName, nestedFieldWidth); +} + +cell getStorageFieldL1(int fieldName, int nestedFieldName) impure method_id { + return cl::get( + getStorageFieldL0(fieldName), + nestedFieldName + ); +} + +cell getStorageFieldL1(int fieldName, int nestedFieldName) impure method_id { + return cl::get( + getStorageFieldL0(fieldName), + nestedFieldName + ); +} + +;; returns storage.fieldName[key] +cell getStorageFieldL1(int fieldName, int key) impure method_id { + (cell field, int exists) = cl::dict256::get( + getStorageFieldL0(fieldName), + key + ); + if (exists) { + return field; + } + return cl::nullObject(); +} + +int getStorageFieldL1(int fieldName, int key) impure method_id { + (int field, int exists) = cl::dict256::get( + getStorageFieldL0(fieldName), + key + ); + if (exists) { + return field; + } + return -1; +} + +;; Level 2: returns storage.fieldName[outerKey][innerKey] +cell getStorageFieldL2(int fieldName, int outerKey, int innerKey) impure method_id { + (cell field, int exists) = cl::dict256::get( + getStorageFieldL1(fieldName, outerKey), + innerKey + ); + if (exists) { + return field; + } + return cl::nullObject(); +} + +int getStorageFieldL2(int fieldName, int outerKey, int innerKey) impure method_id { + (int field, int exists) = cl::dict256::get( + getStorageFieldL1(fieldName, outerKey), + innerKey + ); + if (exists) { + return field; + } + return -1; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/constants.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/constants.fc new file mode 100644 index 00000000..e585d4b9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/constants.fc @@ -0,0 +1,64 @@ +const int ERRORCODE_MASK = 0x7ff; +const int CLASSLIB::ERROR::INVALID_FIELD_TYPE = 1059; +const int CLASSLIB::ERROR::WRONG_ORDER_CONSTRUCTOR = 1060; + +const int MAX_U8 = 0xFF; +const int MAX_U16 = 0xFFFF; +const int MAX_U32 = 0xFFFFFFFF; +const int MAX_U64 = 0xFFFFFFFFFFFFFFFF; +const int MAX_U128 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; +const int MAX_U256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; +const int MAX_COINS = 1329227995784915872903807060280344575; ;; 2^120 - 1 + +const int ADDR_TYPE_NONE = 0; +const int ADDR_TYPE_EXTERN = 1; +const int ADDR_TYPE_STD = 2; +const int ADDR_TYPE_VAR = 3; + +const int TRUE = -1; +const int FALSE = 0; +const int MASTERCHAIN = -1; +const int BASECHAIN = 0; + +;; 0b011000 tag - 0, ihr_disabled - 1, bounce - 1, bounced - 0, src = adr_none$00 +const int SEND_MSG_BOUNCEABLE = 0x18; +;; 0b010000 tag - 0, ihr_disabled - 1, bounce - 0, bounced - 0, src = adr_none$00 +const int SEND_MSG_NON_BOUNCEABLE = 0x10; + +;; MODIFIER +const int NORMAL = 0; +const int PAID_EXTERNALLY = 1; +const int IGNORE_ERRORS = 2; + +;; SEND MODES +const int BOUNCE_IF_FAIL = 16; +const int DESTROY_IF_ZERO = 32; +const int CARRY_REMAINING_GAS = 64; +const int CARRY_ALL_BALANCE = 128; + +;; SENDMSG modes +const int SUB_BALANCE_MSG = 64; +const int SUB_BALANCE_CONTRACT = 128; +const int ONLY_ESTIMATE_FEES = 1024; + +;; SEND MODES QUIETED +const int QDESTROY_IF_ZERO = 34; +const int QCARRY_REMAINING_GAS = 66; +const int QCARRY_ALL_BALANCE = 130; + +const int RESERVE_EXACTLY = 0; +const int RESERVE_ALL_EXCEPT = 1; +const int RESERVE_AT_MOST = 2; +const int EXTRN_DO_NOT_FAIL = 2; +const int BALANCE_INCREASED = 4; +const int BALANCE_DECREASED = 8; +const int RESERVE_BOUNCE_ON_ACTION_FAIL = 16; + +;; a lot of different constants, because arithmetic manipulation of constants is not optimized +const int MAX_CELL_BITS = 1023; +const int MAX_CELL_BYTES = 127; +const int MAX_CELL_WHOLE_BYTE_BITS = MAX_CELL_BYTES * 8; +const int MAX_CELL_BIT_INDEX = 1022; +const int MAX_CELL_REFS = 4; + +const int NULLADDRESS = 0; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/contractMain.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/contractMain.fc new file mode 100644 index 00000000..37628df6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/contractMain.fc @@ -0,0 +1,81 @@ +;;; ================================================================ +;; The base main function for LayerZero Endpoint, UltraLightNode, and OApp +;;; ================================================================ + +#include "handlerCore.fc"; +#include "abstract/contractMainAbstract.fc"; + +;;; =============================== +;; Base main - low-level builtin context + +() main(int myBalance, int msgValue, cell inMsgFull, slice inMsgBody) impure inline { + initTxnContext(myBalance, msgValue, inMsgFull, inMsgBody); + + if (txnIsBounced()) { + return (); + } + + authenticateIfNecessary(); + + ;; ignore empty messages + if (inMsgBody.slice_empty?()) { + return (); + } + + int op = getOpcode(); + cell $md = getMsgData(); + + checkPermissions(op, $md); + + if (op == BaseInterface::OP::EVENT) { + throw(BaseInterface::ERROR::eventEmitted); + } + + tuple actions = null(); + if (op == BaseInterface::OP::INITIALIZE) { + actions = initialize($md); + } elseif (op == BaseInterface::OP::EMPTY) { + actions = emptyActions(); + } else { + assertInitialized(); + actions = _executeOpcode(op, $md); + } + + int outflowNanos = actions.at(ACTIONS_OUTFLOW); + ;; Storage fees are deducted from the contract balance + ;; Any amount that is explicitly deposited into this contract (getRentNanos()) + ;; is reserved to prevent it from being sent downstream + int baseline = (getContractBalance() - storage_fees()) - (getMsgValue() - getDonationNanos()); + ;; The below assertion matches the insufficient ton behavior on action phase + ;; And it's probably unnecessary but it doesn’t cost much gas so no harm in keeping it. + throw_unless(37, baseline >= outflowNanos); + raw_reserve(baseline - outflowNanos, RESERVE_EXACTLY); + + ;; Whether there is any value left to refund to the origin + int msgValueRemaining = true; + ;; the index of the action to be processed + int actionIndex = 1; + int numActions = actions.tlen(); + while (actionIndex < numActions) { + ;; ======================================== + ;; Loop management + tuple action = actions.tuple_at(actionIndex); + int actionType = action.int_at(0); ;; name is always the first index + actionIndex += 1; + + ;; Applies a moving flag where if a single action returns false, then the false flag persists + msgValueRemaining = msgValueRemaining & _executeAction(actionType, action); + } + + ;; If any value remains, we should refund it to the origin + if (msgValueRemaining) { + cell msg = begin_cell() + .store_uint(SEND_MSG_NON_BOUNCEABLE, 6) + .store_slice(getOriginStd()) + .store_coins(0) + .store_uint(1, 107) + .store_ref(empty_cell()) + .end_cell(); + send_raw_message(msg, CARRY_ALL_BALANCE); + } +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/AddressList.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/AddressList.fc new file mode 100644 index 00000000..ef649f44 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/AddressList.fc @@ -0,0 +1,89 @@ +#include "../utils.fc"; + +const int AddressList::addressBits = 256; +;; you can create an address list from a tuple of addresses [integers] +cell AddressList::serialize(tuple addressList) impure inline { + tuple addressListBuilder = unsafeTuple([begin_cell()]); + int idx = 0; + while (idx < addressList.tlen()) { + builder curBuilder = addressListBuilder.at(addressListBuilder.tlen() - 1); + if ((curBuilder.builder_bits() + AddressList::addressBits) > MAX_CELL_BITS) { + addressListBuilder = addressListBuilder.tpush(begin_cell()); + curBuilder = addressListBuilder.at(addressListBuilder.tlen() - 1); + } + addressListBuilder = addressListBuilder + .tset( + addressListBuilder.tlen() - 1, + curBuilder.store_uint256(addressList.int_at(idx)) + ); + idx = idx + 1; + } + builder ret = addressListBuilder~tpop(); + while (addressListBuilder.tlen() > 0) { + builder head = addressListBuilder~tpop(); + ret = head.store_ref(ret.end_cell()); + } + return ret.end_cell(); +} + +;; the "iterator" is the remaining slice +(slice, int) ~AddressList::next(slice addressListSlice) impure inline { + if (addressListSlice.slice_bits() == 0) { + if (addressListSlice.slice_refs_empty?()) { + return (addressListSlice, NULLADDRESS); + } else { + addressListSlice = addressListSlice.preload_first_ref().begin_parse(); + } + } + return addressListSlice.load_uint256(); +} + +;; Check that the addresslist is a valid linked list of addresses with no extra refs or bytes +int AddressList::isValid(cell addressList, int maxCount) impure { + slice addressListSlice = addressList.begin_parse(); + int count = 0; + while (addressListSlice.slice_empty?() == false) { + (int bits, int refs) = addressListSlice.slice_bits_refs(); + if ((refs > 1) | ((bits % 256) != 0)) { + return false; + } + repeat (bits / 256) { + count += 1; + int address = addressListSlice~load_uint256(); + if ((address == NULLADDRESS) | (count > maxCount)) { + return false; + } + } + if (addressListSlice.slice_refs() > 0) { + addressListSlice = addressListSlice.preload_first_ref().begin_parse(); + } + } + + return true; +} + +int AddressList::includes(int address, slice addressListSlice) impure inline { + int storedAddress = addressListSlice~AddressList::next(); + while (storedAddress > NULLADDRESS) { + if (storedAddress == address) { + return true; + } + storedAddress = addressListSlice~AddressList::next(); + } + return false; +} + +int AddressList::length(cell addressList) impure inline { + slice addressListSlice = addressList.begin_parse(); + int count = 0; + int dvnAddress = addressListSlice~AddressList::next(); + while (dvnAddress > NULLADDRESS) { + count += 1; + dvnAddress = addressListSlice~AddressList::next(); + } + return count; +} + +cell AddressList::empty() impure inline { + return empty_cell(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/DeterministicInsertionCircularQueue.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/DeterministicInsertionCircularQueue.fc new file mode 100644 index 00000000..02efc0b6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/DeterministicInsertionCircularQueue.fc @@ -0,0 +1,154 @@ +;;; =================== DeterministicInsertionCircularQueue.fc =================== +;; the DeterministicInsertionCircularQueue is a deterministic-gas circular buffer +;; that stores key-value pairs along with one piece of metadata (8-bits) per entry. +#include "../utils.fc"; + +const int DeterministicInsertionCircularQueue::statusBits = 8; +const int DeterministicInsertionCircularQueue::keyBits = 64; + +const int DeterministicInsertionCircularQueue::invalidKey = -1; +const int DeterministicInsertionCircularQueue::invalidStatus = -1; +const int DeterministicInsertionCircularQueue::ERROR::invalidObject = 1185; + +cell DeterministicInsertionCircularQueue::_buildLevel(int level, cell initialContents) impure inline { + if (level == 0) { + return begin_cell() + .store_ref(initialContents) + .store_ref(initialContents) + .store_ref(initialContents) + .store_ref(initialContents) + .end_cell(); + } else { + int nextLevel = level - 1; + return begin_cell() + .store_ref(DeterministicInsertionCircularQueue::_buildLevel(nextLevel, initialContents)) + .store_ref(DeterministicInsertionCircularQueue::_buildLevel(nextLevel, initialContents)) + .store_ref(DeterministicInsertionCircularQueue::_buildLevel(nextLevel, initialContents)) + .store_ref(DeterministicInsertionCircularQueue::_buildLevel(nextLevel, initialContents)) + .end_cell(); + } +} + +;; Given a well-formed commit_verification_queue +;; get the content of the queue at a given relative nonce +;; (actual key, entry, status, exists) +(int, cell, int, int) DeterministicInsertionCircularQueue::get(cell self, int key) impure inline { + int position = key % MAX_CELL_BITS; + + slice commitSlice = self + .begin_parse() + .preload_ref_at(position / 256) + .begin_parse() + .preload_ref_at((position % 256) / 64) + .begin_parse() + .preload_ref_at((position % 64) / 16) + .begin_parse() + .preload_ref_at((position % 16) / 4) + .begin_parse() + .preload_ref_at(position % 4) + .begin_parse(); + + ;; guaranteed to always have state stored + int exists = commitSlice~load_bool(); + int state = commitSlice~load_uint(DeterministicInsertionCircularQueue::statusBits); + if (exists) { + return ( + commitSlice.preload_uint(DeterministicInsertionCircularQueue::keyBits), + commitSlice.preload_first_ref(), + state, + exists + ); + } + return ( + DeterministicInsertionCircularQueue::invalidKey, + empty_cell(), + DeterministicInsertionCircularQueue::invalidStatus, + exists + ); +} + +cell DeterministicInsertionCircularQueue::_setRaw(cell self, int key, cell newLeaf) impure inline { + int slot = key % MAX_CELL_BITS; + slice queueSlice = self.begin_parse(); + int l0_idx = slot / 256; + int l1_idx = (slot % 256) / 64; + int l2_idx = (slot % 64) / 16; + int l3_idx = (slot % 16) / 4; + int leaf_idx = slot % 4; + + slice l0Slice = queueSlice.preload_ref_at(l0_idx).begin_parse(); + slice l1Slice = l0Slice.preload_ref_at(l1_idx).begin_parse(); + slice l2Slice = l1Slice.preload_ref_at(l2_idx).begin_parse(); + slice l3Slice = l2Slice.preload_ref_at(l3_idx).begin_parse(); + + cell new_l3 = begin_cell() + .store_slice(scutfirst(l3Slice, 0, leaf_idx)) + .store_ref(newLeaf) + .store_slice(scutlast(l3Slice, 0, 3 - leaf_idx)) + .end_cell(); + + cell new_l2 = begin_cell() + .store_slice(scutfirst(l2Slice, 0, l3_idx)) + .store_ref(new_l3) + .store_slice(scutlast(l2Slice, 0, 3 - l3_idx)) + .end_cell(); + + cell new_l1 = begin_cell() + .store_slice(scutfirst(l1Slice, 0, l2_idx)) + .store_ref(new_l2) + .store_slice(scutlast(l1Slice, 0, 3 - l2_idx)) + .end_cell(); + + cell new_l0 = begin_cell() + .store_slice(scutfirst(l0Slice, 0, l1_idx)) + .store_ref(new_l1) + .store_slice(scutlast(l0Slice, 0, 3 - l1_idx)) + .end_cell(); + + return ( + begin_cell() + .store_slice(scutfirst(queueSlice, 0, l0_idx)) + .store_ref(new_l0) + .store_slice(scutlast(queueSlice, 0, 3 - l0_idx)) + .end_cell() + ); +} + +;; self +cell DeterministicInsertionCircularQueue::set(cell self, int key, cell $obj, int newState) impure inline { + return DeterministicInsertionCircularQueue::_setRaw( + self, + key, + begin_cell() + .store_bool(true) ;; occupied + .store_uint(newState, DeterministicInsertionCircularQueue::statusBits) + .store_uint(key, DeterministicInsertionCircularQueue::keyBits) + .store_ref($obj) + .end_cell() + ); +} + +cell DeterministicInsertionCircularQueue::delete(cell self, int key) impure inline { + return DeterministicInsertionCircularQueue::_setRaw( + self, + key, + begin_cell() + .store_bool(false) ;; occupied + .store_uint(0, DeterministicInsertionCircularQueue::statusBits) + .store_uint(0, DeterministicInsertionCircularQueue::keyBits) + .store_ref(empty_cell()) + .end_cell() + ); +} + +cell DeterministicInsertionCircularQueue::create() impure method_id { + ;; ceil(log4(MAX_CELL_BITS)) == 4 + ;; build the initial contents of each leaf in the outer scope to save gas + cell initialContents = begin_cell() + .store_bool(false) ;; unoccupied + .store_uint(0, DeterministicInsertionCircularQueue::statusBits) ;; value not used until set + .store_uint(0, DeterministicInsertionCircularQueue::keyBits) ;; value not used until set + .store_ref(empty_cell()) ;; value not used until set + .end_cell(); + return DeterministicInsertionCircularQueue::_buildLevel(4, initialContents); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/PipelinedOutOfOrder.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/PipelinedOutOfOrder.fc new file mode 100644 index 00000000..c9258278 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/dataStructures/PipelinedOutOfOrder.fc @@ -0,0 +1,132 @@ +;;; ============================================================================== +;; Pipelined Out-of-Order data structure +;; this data structure defines a bitmap that is used to track a stream of +;; commands that are executed out-of-order in a bounded-depth pipeline +#include "../classlib.fc"; + +const int POOO::NAME = "POOO"u; + +const int POOO::nextEmpty = 0; +const int POOO::bitmap = 1; + +const int POOO::ERROR::negativeIndex = 1153; + +cell POOO::New() impure inline method_id { + return cl::declare( + POOO::NAME, + unsafeTuple([ + [cl::t::uint64, 1], ;; nextEmpty + [cl::t::cellRef, begin_cell().store_zeroes(MAX_CELL_BITS).end_cell()] ;; bitmap + ]) + ); +} + +const int POOO::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 2); +const int POOO::_headerFillerBits = _HEADER_WIDTH - POOO::_headerInfoBits; +const int POOO::_headerInfo = 92590899976783941628; + +cell POOO::buildFull(int nextEmpty, cell bitmap) impure inline { + return begin_cell() + .store_uint(POOO::_headerInfo, POOO::_headerInfoBits) ;; header info + .store_ones(POOO::_headerFillerBits) ;; header filler + .store_uint64(nextEmpty) ;; nextEmpty + .store_ref(bitmap) ;; bitmap + .end_cell(); +} + +;; ========================== Object Getters ========================== + +const int POOO::_nextEmptyOffset = _HEADER_WIDTH; + +int POOO::getNextEmpty(cell $self) impure inline { + return $self.cellPreloadUint64At(POOO::_nextEmptyOffset); +} + +int POOO::maxSettableBit(cell $self) impure inline { + return $self.POOO::getNextEmpty() + MAX_CELL_BIT_INDEX; +} + +;; ========================== Object Multi-Getters ========================== + +;; returns (nextEmpty, bitmap.slice) +(int, slice) POOO::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint64At(POOO::_nextEmptyOffset), + selfSlice.preloadRefSliceAt(0) + ); +} + +;; ========================== Object Utils ========================== + +int POOO::isBitSet(cell $self, int absoluteIdx) impure inline { + (int nextEmpty, slice bitmap) = POOO::deserialize($self); + int relativeIdx = absoluteIdx - nextEmpty; + return bitmap.preloadBoolAt(relativeIdx); +} + +;; ========================== Object Setters ========================== + +;; Algorithm: +;; 1. Extract the first `index` bits from the original bitmap. +;; 2. Append a '1' bit to the extracted bits. +;; 3. Append the remaining bits from the original bitmap after the first `index` bits to form a new bitmap. + +;; Returns: +;; 1. The number of leading ones (`leadingOnes`). +;; 2. A new cell that contains: +;; - The remaining bits after the leading ones from the original bitmap. +;; - A number of trailing zero bits equal to `leadingOnes`. + +;; Parameters: +;; - `bitmapCell`: The original bitmap in the form of a cell. +;; - `index`: The position up to which the first part of the bitmap is extracted. +cell POOO::set(cell $self, int absoluteIndex) impure inline method_id { + throw_if(POOO::ERROR::negativeIndex, absoluteIndex < 0); + + (int nextEmpty, slice bitmapSlice) = POOO::deserialize($self); + if (absoluteIndex < nextEmpty) { + return $self; + } + + int index = absoluteIndex - nextEmpty; + + (int leadingOnes, slice remainingBitmap) = ldones( + begin_cell() + .store_slice(scutfirst(bitmapSlice, index, 0)) + .store_uint(1, 1) + .store_slice(scutlast(bitmapSlice, MAX_CELL_BITS - index - 1, 0)) + .as_slice() + ); + + return POOO::buildFull( + nextEmpty + leadingOnes, + begin_cell() + .store_slice(remainingBitmap) + .store_zeroes(leadingOnes) + .end_cell() + ); +} + +cell POOO::unsafeSetBits(cell $self, int start, int end) impure inline method_id { + (int nextEmpty, slice bitmapSlice) = POOO::deserialize($self); + + int startIndex = start - nextEmpty; + int endIndex = end - nextEmpty; + + (int leadingOnes, slice remainingBitmap) = ldones( + begin_cell() + .store_slice(scutfirst(bitmapSlice, startIndex, 0)) + .store_ones(endIndex - startIndex) + .store_slice(scutlast(bitmapSlice, MAX_CELL_BITS - endIndex, 0)) + .as_slice() + ); + + return POOO::buildFull( + nextEmpty + leadingOnes, + begin_cell() + .store_slice(remainingBitmap) + .store_zeroes(leadingOnes) + .end_cell() + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/handlerCore.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/handlerCore.fc new file mode 100644 index 00000000..930e8976 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/handlerCore.fc @@ -0,0 +1,27 @@ +;;; ========================================== +;; This file contains the utility functions for all handler functions that follow +;; LayerZero Labs handler convention. +;;; ========================================== +#include "abstract/handlerAbstract.fc"; + +#include "actions/utils.fc"; + +#include "baseInterface.fc"; + +;;; ===================REQUIRED VIRTUAL FUNCTIONS======================= + +(cell, tuple) preamble() impure inline { + return (getContractStorage(), emptyActions()); +} + +() checkPermissions(int op, cell $md) impure inline { + if (op == BaseInterface::OP::EVENT) { + return (); + } elseif (op == BaseInterface::OP::INITIALIZE) { + assertOwner(); + } elseif (op == BaseInterface::OP::EMPTY) { + return (); + } else { + _checkPermissions(op, $md); + } +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/stdlib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/stdlib.fc new file mode 100644 index 00000000..b02c8011 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/stdlib.fc @@ -0,0 +1,640 @@ +;; Standard library for funC +;; + +{- + This file is part of TON FunC Standard Library. + + FunC Standard Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + FunC Standard Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + +-} + +{- + # Tuple manipulation primitives + The names and the types are mostly self-explaining. + See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) + for more info on the polymorphic functions. + + Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) + and vise versa. +-} + +{- + # Lisp-style lists + + Lists can be represented as nested 2-elements tuples. + Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). + For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. +-} + +;;; Adds an element to the beginning of lisp-style list. +forall X -> tuple cons(X head, tuple tail) asm "CONS"; + +;;; Extracts the head and the tail of lisp-style list. +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; + +;;; Extracts the tail and the head of lisp-style list. +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; + +;;; Returns the head of lisp-style list. +forall X -> X car(tuple list) asm "CAR"; + +;;; Returns the tail of lisp-style list. +tuple cdr(tuple list) asm "CDR"; + +;;; Creates tuple with zero elements. +tuple empty_tuple() asm "NIL"; + +;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` +;;; is of length at most 255. Otherwise throws a type check exception. +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; + +;;; Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) asm "SINGLE"; + +;;; Unpacks a tuple of length one +forall X -> X unsingle([X] t) asm "UNSINGLE"; + +;;; Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; + +;;; Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; + +;;; Creates a tuple of length three with given arguments as elements. +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; + +;;; Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; + +;;; Creates a tuple of length four with given arguments as elements. +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; + +;;; Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; + +;;; Returns the first element of a tuple (with unknown element types). +forall X -> X first(tuple t) asm "FIRST"; + +;;; Returns the second element of a tuple (with unknown element types). +forall X -> X second(tuple t) asm "SECOND"; + +;;; Returns the third element of a tuple (with unknown element types). +forall X -> X third(tuple t) asm "THIRD"; + +;;; Returns the fourth element of a tuple (with unknown element types). +forall X -> X fourth(tuple t) asm "3 INDEX"; + +;;; Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; + +;;; Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; + +;;; Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; + +;;; Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; + +;;; Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; + + +;;; Push null element (casted to given type) +;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. +;;; So `null` can actually have any atomic type. +forall X -> X null() asm "PUSHNULL"; + +;;; Moves a variable [x] to the top of the stack +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + + + +;;; Returns the current Unix time as an Integer +int now() asm "NOW"; + +;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. +slice my_address() asm "MYADDR"; + +;;; Returns the balance of the smart contract as a tuple consisting of an int +;;; (balance in nanotoncoins) and a `cell` +;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") +;;; at the start of Computation Phase. +;;; Note that RAW primitives such as [send_raw_message] do not update this field. +[int, cell] get_balance() asm "BALANCE"; + +;;; Returns the logical time of the current transaction. +int cur_lt() asm "LTIME"; + +;;; Returns the starting logical time of the current block. +int block_lt() asm "BLOCKLT"; + +;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. +int cell_hash(cell c) asm "HASHCU"; + +;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created +;;; and its hash computed by [cell_hash]. +int slice_hash(slice s) asm "HASHSU"; + +;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. +int string_hash(slice s) asm "SHA256U"; + +{- + # Signature checks +-} + +;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +;;; using [public_key] (also represented by a 256-bit unsigned integer). +;;; The signature must contain at least 512 data bits; only the first 512 bits are used. +;;; The result is `−1` if the signature is valid, `0` otherwise. +;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, +;;; the second hashing occurring inside `CHKSIGNS`. +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; + +;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, +;;; similarly to [check_signature]. +;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +;;; The verification of Ed25519 signatures is the standard one, +;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +{--- + # Computation of boc size + The primitives below may be useful for computing storage fees of user-provided data. +-} + +;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +;;; the identification of equal cells. +;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. +;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; +;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; + +;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. +;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; + +;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. +(int, int, int, int) slice_compute_data_size?(slice s, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +{-- + # Debug primitives + Only works for local TVM execution with debug level verbosity +-} +;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. +() dump_stack() impure asm "DUMPSTK"; + +{- + # Persistent storage save and load +-} + +;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. +cell get_data() asm "c4 PUSH"; + +;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +() set_data(cell c) impure asm "c4 POP"; + +{- + # Continuation primitives +-} +;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. +;;; The primitive returns the current value of `c3`. +cont get_c3() impure asm "c3 PUSH"; + +;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +;;; Note that after execution of this primitive the current code +;;; (and the stack of recursive function calls) won't change, +;;; but any other function call will use a function from the new code. +() set_c3(cont c) impure asm "c3 POP"; + +;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. +cont bless(slice s) impure asm "BLESS"; + +{--- + # Gas related primitives +-} + +;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +;;; decreasing the value of `gr` by `gc` in the process. +;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. +;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. +;;; +;;; For more details check [accept_message effects](https://docs.ton.org/develop/smart-contracts/guidelines/accept). +() accept_message() impure asm "ACCEPT"; + +;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +;;; an (unhandled) out of gas exception is thrown before setting new gas limits. +;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; + +;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +;;; so that the current execution is considered “successful” with the saved values even if an exception +;;; in Computation Phase is thrown later. +() commit() impure asm "COMMIT"; + +;;; Not implemented +;;() buy_gas(int gram) impure asm "BUYGAS"; + +;;; Computes the amount of gas that can be bought for `amount` nanoTONs, +;;; and sets `gl` accordingly in the same way as [set_gas_limit]. +() buy_gas(int amount) impure asm "BUYGAS"; + +;;; Computes the minimum of two integers [x] and [y]. +int min(int x, int y) asm "MIN"; + +;;; Computes the maximum of two integers [x] and [y]. +int max(int x, int y) asm "MAX"; + +;;; Sorts two integers. +(int, int) minmax(int x, int y) asm "MINMAX"; + +;;; Computes the absolute value of an integer [x]. +int abs(int x) asm "ABS"; + +{- + # Slice primitives + + It is said that a primitive _loads_ some data, + if it returns the data and the remainder of the slice + (so it can also be used as [modifying method](https://docs.ton.org/develop/func/statements#modifying-methods)). + + It is said that a primitive _preloads_ some data, if it returns only the data + (it can be used as [non-modifying method](https://docs.ton.org/develop/func/statements#non-modifying-methods)). + + Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. +-} + + +;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. +slice begin_parse(cell c) asm "CTOS"; + +;;; Checks if [s] is empty. If not, throws an exception. +() end_parse(slice s) impure asm "ENDS"; + +;;; Loads the first reference from the slice. +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; + +;;; Preloads the first reference from the slice. +cell preload_ref(slice s) asm "PLDREF"; + +{- Functions below are commented because are implemented on compilator level for optimisation -} + +;;; Loads a signed [len]-bit integer from a slice [s]. +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; + +;;; Loads an unsigned [len]-bit integer from a slice [s]. +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; + +;;; Preloads a signed [len]-bit integer from a slice [s]. +;; int preload_int(slice s, int len) asm "PLDIX"; + +;;; Preloads an unsigned [len]-bit integer from a slice [s]. +;; int preload_uint(slice s, int len) asm "PLDUX"; + +;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; + +;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; + +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^120 - 1`). +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + +;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; + +;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice first_bits(slice s, int len) asm "SDCUTFIRST"; + +;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; + +;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. +;;; (returns `null` if `nothing` constructor is used). +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; + +;;; Preloads a dictionary `D` from `slice` [s]. +cell preload_dict(slice s) asm "PLDDICT"; + +;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. +slice skip_dict(slice s) asm "SKIPDICT"; +(slice, ()) ~skip_dict(slice s) asm "SKIPDICT"; + +;;; Loads (Maybe ^Cell) from `slice` [s]. +;;; In other words loads 1 bit and if it is true +;;; loads first ref and return it with slice remainder +;;; otherwise returns `null` and slice remainder +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; + +;;; Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; + + +;;; Returns the depth of `cell` [c]. +;;; If [c] has no references, then return `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +;;; If [c] is a `null` instead of a cell, returns zero. +int cell_depth(cell c) asm "CDEPTH"; + + +{- + # Slice size primitives +-} + +;;; Returns the number of references in `slice` [s]. +int slice_refs(slice s) asm "SREFS"; + +;;; Returns the number of data bits in `slice` [s]. +int slice_bits(slice s) asm "SBITS"; + +;;; Returns both the number of data bits and the number of references in `slice` [s]. +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; + +;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). +int slice_empty?(slice s) asm "SEMPTY"; + +;;; Checks whether `slice` [s] has no bits of data. +int slice_data_empty?(slice s) asm "SDEMPTY"; + +;;; Checks whether `slice` [s] has no references. +int slice_refs_empty?(slice s) asm "SREMPTY"; + +;;; Returns the depth of `slice` [s]. +;;; If [s] has no references, then returns `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +int slice_depth(slice s) asm "SDEPTH"; + +{- + # Builder size primitives +-} + +;;; Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) asm "BREFS"; + +;;; Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) asm "BBITS"; + +;;; Returns the depth of `builder` [b]. +;;; If no cell references are stored in [b], then returns 0; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +int builder_depth(builder b) asm "BDEPTH"; + +{- + # Builder primitives + It is said that a primitive _stores_ a value `x` into a builder `b` + if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. + It can be used as [non-modifying method](https://docs.ton.org/develop/func/statements#non-modifying-methods). + + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. +-} + +;;; Creates a new empty `builder`. +builder begin_cell() asm "NEWC"; + +;;; Converts a `builder` into an ordinary `cell`. +cell end_cell(builder b) asm "ENDC"; + +;;; Stores a reference to `cell` [c] into `builder` [b]. +builder store_ref(builder b, cell c) asm(c b) "STREF"; + +;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; + +;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; + + +;;; Stores `slice` [s] into `builder` [b] +builder store_slice(builder b, slice s) asm "STSLICER"; + +;;; Stores (serializes) an integer [x] in the range `0..2^120 − 1` into `builder` [b]. +;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, +;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +;;; followed by an `8l`-bit unsigned big-endian representation of [x]. +;;; If [x] does not belong to the supported range, a range check exception is thrown. +;;; +;;; Store amounts of TonCoins to the builder as VarUInteger 16 +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) asm "STGRAMS"; + +;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. +;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +;;; Stores (Maybe ^Cell) to builder: +;;; if cell is null store 1 zero bit +;;; otherwise store 1 true bit and ref to cell +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + + +{- + # Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. +-} + +;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, +;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; + +;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. +tuple parse_addr(slice s) asm "PARSEMSGADDR"; + +;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, +;;; and returns both the workchain and the 256-bit address as integers. +;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +;;; throws a cell deserialization exception. +(int, int) parseStdAddress(slice s) asm "REWRITESTDADDR"; + +;;; A variant of [parseStdAddress] that returns the (rewritten) address as a slice [s], +;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +{- + # Dictionary primitives +-} + + +;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; + +;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; + +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; + +;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL +cell new_dict() asm "NEWDICT"; +;;; Checks whether a dictionary is empty. Equivalent to cell_null?. +int dict_empty?(cell c) asm "DICTEMPTY"; + + +{- Prefix dictionary primitives -} +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. +cell config_param(int x) asm "CONFIGOPTPARAM"; +;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. +int cell_null?(cell c) asm "ISNULL"; + +;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract +() set_code(cell new_code) impure asm "SETCODE"; + +;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. +int random() impure asm "RANDU256"; +;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. +int rand(int range) impure asm "RAND"; +;;; Returns the current random seed as an unsigned 256-bit Integer. +int get_seed() impure asm "RANDSEED"; +;;; Sets the random seed to unsigned 256-bit seed. +() set_seed(int) impure asm "SETRAND"; +;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. +() randomize(int x) impure asm "ADDRAND"; +;;; Equivalent to randomize(cur_lt());. +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +;;; Checks whether the data parts of two slices coinside +int equal_slice_bits(slice a, slice b) asm "SDEQ"; + +;;; Concatenates two builders +builder store_builder(builder to, builder from) asm "STBR"; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/stringlib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/stringlib.fc new file mode 100644 index 00000000..15ce6615 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/stringlib.fc @@ -0,0 +1,73 @@ +#include "utils.fc"; + +;;; ===============================STRING MANIPULATION FUNCTIONS=========================== +;; note that these functions are NOT optimized and should NOT be used in production code + +const int ASCII_ZERO = 48; +const int ASCII_MASK = 0x1313131313131313131313131313131313131313131313131313131313131313; +const int ASCII_A = 65; + +slice str::asciiUint256ToStr(int asciiUint256) impure { + int leading_zeroes = _SDCNTLEAD0(begin_cell().store_uint256(asciiUint256).end_cell().begin_parse()); + int trailing_bits = 256 - leading_zeroes; + int mask = POW2(trailing_bits) - 1; + return begin_cell().store_uint256(asciiUint256 | (ASCII_MASK & (~ mask))).end_cell().begin_parse(); +} + +(slice, ()) ~str::concat(slice self, slice other) impure { + if(self.slice_bits() + other.slice_bits() > 127 * MAX_U8) { + throwError("Cannot concatenate: string too long"); + } + return (begin_cell().store_slice(self).store_slice(other).end_cell().begin_parse(), ()); +} + +slice str::concat(slice self, slice other) impure { + self~str::concat(other); + return self; +} + +(slice, ()) ~str::concatInt(slice self, int val) impure { + slice intSlice = empty_slice(); + if (val < 0) { + self~str::concat("-"); + val = -1 * val; + } + if (val == 0) { + intSlice~str::concat(begin_cell().store_uint8(ASCII_ZERO).end_cell().begin_parse()); + } + while (val > 0) { + intSlice = begin_cell().store_uint8(ASCII_ZERO + val % 10).end_cell().begin_parse().str::concat(intSlice); + val /= 10; + } + return (self.str::concat(intSlice), ()); +} + +slice str::concatInt(slice self, int val) impure { + self~str::concatInt(val); + return self; +} + +(slice, ()) ~str::concatHex(slice self, int val) impure { + slice hexSlice = empty_slice(); + if (val == 0) { + hexSlice~str::concat(begin_cell().store_uint8(ASCII_ZERO).end_cell().begin_parse()); + } + while (val > 0) { + if (val % 16 <= 9) { + hexSlice = begin_cell().store_uint8(ASCII_ZERO + val % 16).end_cell().begin_parse().str::concat(hexSlice); + } else { + hexSlice = begin_cell().store_uint8(ASCII_A + val % 16 - 10).end_cell().begin_parse().str::concat(hexSlice); + } + val = (val >> 4); ;; val /= 16 + } + return (self.str::concat(hexSlice), ()); +} + +slice str::concatHex(slice self, int val) impure { + self~str::concatHex(val); + return self; +} + +() str::console::log(slice string, int val) impure { + ~strdump(string.str::concat(": ").str::concatInt(val)); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/POOO/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/POOO/main.fc new file mode 100644 index 00000000..aec06ae2 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/POOO/main.fc @@ -0,0 +1,153 @@ +#include "../../../../tests/testMain.fc"; +#include "../../dataStructures/PipelinedOutOfOrder.fc"; + +slice _testName() { return "POOO"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +(int, slice) POOO::setBitOne(cell $args) impure { + return test::shouldBeTrue( + POOO::New() + .POOO::set(1) + .cl::get(POOO::nextEmpty) == 2 + ); +} + +(int, slice) POOO::setBitTwo(cell $args) impure { + cell $pooo = POOO::New().POOO::set(2); + return test::shouldBeTrue( + ($pooo.cl::get(POOO::nextEmpty) == 1) + & $pooo.POOO::isBitSet(2) + ); +} + +(int, slice) POOO::8bitOrdered(cell $args) impure { + cell $pooo = POOO::New() + .POOO::set(1) + .POOO::set(2) + .POOO::set(3) + .POOO::set(4) + .POOO::set(5) + .POOO::set(6) + .POOO::set(7) + .POOO::set(8); + return test::shouldBeTrue( + ($pooo.cl::get(POOO::nextEmpty) == 9) + ); +} + +(int, slice) POOO::8bitUnordered(cell $args) impure { + cell $pooo = POOO::New() + .POOO::set(6) + .POOO::set(2) + .POOO::set(7) + .POOO::set(4) + .POOO::set(8) + .POOO::set(3) + .POOO::set(1) + .POOO::set(5); + return test::shouldBeTrue( + ($pooo.cl::get(POOO::nextEmpty) == 9) + ); +} + +(int, slice) POOO::lastBit(cell $args) impure { + cell $pooo = POOO::New() + .POOO::set(MAX_CELL_BITS); + return test::shouldBeTrue( + $pooo.POOO::isBitSet(MAX_CELL_BITS) + ); +} + +(int, slice) POOO::revert::pastLastBit(cell $args) impure { + int success = false; + try { + cell $pooo = POOO::New().POOO::set(MAX_CELL_BITS + 1); + } catch (x, n) { + success = true; + } + if (success) { + return (TEST_SUCCESS, ""); + } + return (TEST_FAILED, "Should have thrown an exception"); +} + +(int, slice) POOO::indexZero(cell $args) impure { + return test::shouldBeTrue( + POOO::New() + .POOO::set(0) + .cl::get(POOO::nextEmpty) == 1 + ); +} + +(int, slice) POOO::wrapsAtEnd(cell $args) impure { + ;; flip(1) bits starting at index 1 to index 1023 leaving index 0 as unflipped(0) + ;; Binary Representation: 011111...111111 + cell $pooo = cl::declare( + POOO::NAME, + unsafeTuple([ + [cl::t::uint64, 1], + [cl::t::cellRef, begin_cell() + .store_zeroes(1) + .store_ones(MAX_CELL_BITS - 1) + .end_cell() + ] + ]) + ); + + throwErrorUnless( + $pooo.cl::get(POOO::nextEmpty) == 1, + "nextEmpty should be 1" + ); + + ;; flip(1) index 0 and expect the bitmap to clear all bits + ;; and set nextEmpty to 1024 (MAX_CELL_BITS + 1) + $pooo = $pooo.POOO::set(1); + throwErrorUnless( + $pooo.cl::get(POOO::nextEmpty) == 1024, + "nextEmpty should be 1024" + ); + + ;; flip(1) the nextEmpty nonce and expect it to increment by 1 + $pooo = $pooo.POOO::set(1024); + throwErrorUnless( + $pooo.cl::get(POOO::nextEmpty) == 1025, + "nextEmpty should be 1025" + ); + + ;; flip(1) index 1547 and expect nextEmpty to not change + $pooo = $pooo.POOO::set(1547); + return test::shouldBeTrue( + $pooo.cl::get(POOO::nextEmpty) == 1025 + ); +} + +(int, slice) POOO::negativeInput(cell $args) impure { + int success = false; + try { + POOO::New().POOO::set(-69); + } catch (_, n) { + if(n == POOO::ERROR::negativeIndex) { + success = true; + } + } + if (success) { + return (TEST_SUCCESS, ""); + } + return (TEST_FAILED, "Should have thrown an exception"); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([POOO::setBitOne, "POOO::setBitOne"]) + .tpush([POOO::setBitTwo, "POOO::setBitTwo"]) + .tpush([POOO::8bitOrdered, "POOO::8bitOrdered"]) + .tpush([POOO::8bitUnordered, "POOO::8bitUnordered"]) + .tpush([POOO::lastBit, "POOO::lastBit"]) + .tpush([POOO::revert::pastLastBit, "POOO::revert::pastLastBit"]) + .tpush([POOO::indexZero, "POOO::indexZero"]) + .tpush([POOO::wrapsAtEnd, "POOO::wrapsAtEnd"]) + .tpush([POOO::negativeInput, "POOO::negativeInput"]); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/POOO/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/POOO/serde.fc new file mode 100644 index 00000000..f41956e4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/POOO/serde.fc @@ -0,0 +1,95 @@ +#include "../../dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../../tests/mocks.fc"; + + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "POOO serde"; } + + +int OLD::POOO::isBitSet(cell $self, int absoluteIdx) impure { + int relativeIdx = absoluteIdx - $self.cl::get(POOO::nextEmpty); + return $self + .cl::get(POOO::bitmap) + .begin_parse() + .preload_bits_offset(relativeIdx, 1) + .preload_uint(1) == 1; +} + +;; POOO: has 1 Builder +;; has 2 getters +;; has 1 multi-getter +;; has 2 utils +(int, slice) Serde::POOO::buildFull(cell $unused) impure { + cell $expectedPooo = POOO::New() + .cl::set(POOO::nextEmpty, 100) + .cl::set(POOO::bitmap, _getRandomCode(12)); + + cell $pooo = POOO::buildFull(100, _getRandomCode(12)); + + return test::build::equal( + $expectedPooo, + $pooo + ); +} + +(int, slice) Serde::POOO::maxSettableBit(cell $unused) impure { + cell $pooo = POOO::New(); + + int expected = $pooo.cl::get(POOO::nextEmpty) + MAX_CELL_BIT_INDEX; + int actual = POOO::maxSettableBit($pooo); + + return test::shouldBeTrue(expected == actual); +} + +(int, slice) Serde::POOO::getNextEmpty(cell $unused) impure { + cell $pooo = POOO::New(); + return test::getData::equal( + $pooo, + POOO::getNextEmpty, + POOO::nextEmpty + ); +} + +(int, slice) Serde::POOO::deserialize(cell $unused) impure { + cell $pooo = POOO::New(); + + (int nextEmpty, slice bitmapSlice) = POOO::deserialize($pooo); + cell bitmap = begin_cell().store_slice(bitmapSlice).end_cell(); + + ;; multiget::equal can't check for slices. + return test::multiget::equal( + $pooo, + unsafeTuple([ + POOO::nextEmpty, + POOO::bitmap + ]), + unsafeTuple([ + nextEmpty, + bitmap + ]) + ); +} + +(int, slice) Serde::POOO::isBitSet(cell $unused) impure { + cell $pooo = POOO::New().POOO::set(2); + return test::shouldBeTrue( + POOO::getNextEmpty($pooo) + & (POOO::isBitSet($pooo, 100) == OLD::POOO::isBitSet($pooo, 100)) + & (POOO::isBitSet($pooo, 2) == OLD::POOO::isBitSet($pooo, 2)) + ); +} + +;; POOO::set and POOO::unsafeSetBits are correct if both POOO::buildFull and POOO::deserialize are correct. +;; (which they are as the above tests pass.) + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::POOO::buildFull, "Serde::POOO::buildFull"]) + .tpush([Serde::POOO::maxSettableBit, "Serde::POOO::maxSettableBit"]) + .tpush([Serde::POOO::getNextEmpty, "Serde::POOO::getNextEmpty"]) + .tpush([Serde::POOO::deserialize, "Serde::POOO::deserialize"]) + .tpush([Serde::POOO::isBitSet, "Serde::POOO::isBitSet"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/actions/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/actions/serde.fc new file mode 100644 index 00000000..9b877479 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/actions/serde.fc @@ -0,0 +1,28 @@ +#include "../../actions/event.fc"; +#include "../../../../tests/baseSerdeTest.fc"; + +int _getEventSink() impure inline { + return 0; +} + +tuple _newAction(int topic, cell $body) impure inline { + return action::event::create(topic, $body, empty_cell()); +} + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Actions serde"; } + +;; Event +(int, slice) Serde::action::event::build(cell $unused) impure inline { + return test::build::equal( + action::event::New(456, getContractStorage(), empty_cell()), + action::event::build(456, getContractStorage(), empty_cell()) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::action::event::build, "Serde::action::event::build"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/autoload.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/autoload.fc new file mode 100644 index 00000000..45922ee0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/autoload.fc @@ -0,0 +1,2 @@ +#include "handler.fc"; +#include "storage.fc"; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/handler.fc new file mode 100644 index 00000000..0bff4b02 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/handler.fc @@ -0,0 +1,15 @@ +#include "../../../protocol/core/abstract/protocolHandler.fc"; + +#include "storage.fc"; + +(cell, tuple) _initialize(cell $md) impure inline { + return preamble(); +} + +() _checkPermissions(int op, cell $md) impure inline { + return (); +} + +int _getEventSink() impure inline { + return getContractAddress(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/main.fc new file mode 100644 index 00000000..72797ef8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/main.fc @@ -0,0 +1,122 @@ +#include "./handler.fc"; +#include "../../classlib.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../protocol/core/baseStorage.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "baseContract"; } + +cell createContractStorage() impure { + setContractStorage(baseContract::New(getCaller())); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +;; Anyone can emit an event with "to" this contract +(int, slice) event::checkPermissions::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass(BaseInterface::OP::EVENT, cl::nullObject()); +} + +;; -- Only the owner can initialize the contract +(int, slice) initialize::checkPermissions::success::basic(cell $storage) impure { + return test::permissions::shouldPass(BaseInterface::OP::INITIALIZE, cl::nullObject()); +} + +(int, slice) initialize::checkPermissions::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(BaseInterface::OP::INITIALIZE, cl::nullObject()); +} + +(int, slice) initialize::handler::revert::notAuthenticated(cell $storage) impure { + createContractStorage(); + return test::handler::shouldFail( + initialize, + cl::nullObject(), + BaseInterface::ERROR::notAuthenticated + ); +} + +(int, slice) initialize::handler::success::basic(cell $storage) impure { + createContractStorage(); + authenticate(); + $storage = getContractStorage(); + return test::handler::shouldPass( + initialize, + cl::nullObject(), + emptyActions(), + $storage.cl::set( + BASE_STORAGE_INDEX, + $storage + .cl::get(BASE_STORAGE_INDEX) + .cl::set(BaseStorage::initialized, true) + ), + txnContext + ); +} + +(int, slice) initialize::handler::success::alreadyInitialized(cell $storage) impure { + createInitializedStorage(); + return test::handler::shouldPass( + initialize, + cl::nullObject(), + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) authenticate::handler::revert::alreadyInitialized(cell $storage) impure { + createContractStorage(); + setContractStorage( + getContractStorage().cl::set( + BASE_STORAGE_INDEX, + getContractStorage() + .cl::get(BASE_STORAGE_INDEX) + .cl::set(BaseStorage::initialized, true) + ) + ); + try { + authenticate(); + } catch (x, n) { + if (n != BaseInterface::ERROR::alreadyInitialized) { + return ( + TEST_FAILED, + "actual error: " + .str::concatInt(n) + .str::concat(" != expected: ") + .str::concatInt(BaseInterface::ERROR::alreadyInitialized) + ); + } + } + return (TEST_SUCCESS, ""); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([event::checkPermissions::success::basic, "event::checkPermissions::success::basic"]) + .tpush([initialize::checkPermissions::success::basic, "initialize::checkPermissions::success::basic"]) + .tpush([initialize::checkPermissions::revert::notOwner, "initialize::checkPermissions::revert::notOwner"]) + .tpush([initialize::handler::revert::notAuthenticated, "initialize::handler::revert::notAuthenticated"]) + .tpush([initialize::handler::success::basic, "initialize::handler::success::basic"]) + .tpush([initialize::handler::success::alreadyInitialized, "initialize::handler::success::alreadyInitialized"]) + .tpush([authenticate::handler::revert::alreadyInitialized, "authenticate::handler::revert::alreadyInitialized"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/storage.fc new file mode 100644 index 00000000..21ae5799 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/baseContract/storage.fc @@ -0,0 +1,15 @@ +#include "../../../funC++/classlib.fc"; +#include "../../../protocol/core/baseStorage.fc"; + +const int baseContract::NAME = "baseCntrct"u; + +const int baseContract::baseStorage = 0; + +cell baseContract::New(int owner) impure inline { + return cl::declare( + baseContract::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)] + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/classlib/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/classlib/main.fc new file mode 100644 index 00000000..3c9ae43d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/classlib/main.fc @@ -0,0 +1,517 @@ +#include "../../classlib.fc"; +#include "../../stdlib.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../stringlib.fc"; +#include "../../utils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/msgdata/OptionsExtended.fc"; +#include "../../../classes/msgdata/MdObj.fc"; + +slice _testName() { return "classlib"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +(int, slice) get::success::basic(cell $storage) impure { + cell $child_obj = cl::declare( + "child"u, + unsafeTuple([ + [cl::t::uint256, 0] + ]) + ); + cell my_dict = new_dict(); + my_dict~udict_set( + 256, + 1, + begin_cell().store_uint256(1234).end_cell().begin_parse() + ); + int start_gas = get_gas_consumed(); + cell $obj = cl::declare( + "uints"u, + unsafeTuple([ + [cl::t::uint8, MAX_U8], + [cl::t::uint16, MAX_U16], + [cl::t::uint32, MAX_U32], + [cl::t::uint64, MAX_U64], + [cl::t::coins, MAX_COINS], + [cl::t::uint256, MAX_U256], + [cl::t::address, getContractAddress()], + [cl::t::bool, true], + [cl::t::dict256, my_dict], + [cl::t::objRef, $child_obj], + [cl::t::cellRef, my_dict] + ]) + ); + throwErrorUnless($obj.cl::get(0) == MAX_U8, "uint8"); + throwErrorUnless($obj.cl::get(1) == MAX_U16, "uint16"); + throwErrorUnless($obj.cl::get(2) == MAX_U32, "uint32"); + throwErrorUnless($obj.cl::get(3) == MAX_U64, "uint64"); + throwErrorUnless($obj.cl::get(4) == MAX_COINS, "coins"); + throwErrorUnless($obj.cl::get(5) == MAX_U256, "uint256"); + throwErrorUnless($obj.cl::get
(6) == getContractAddress(), "address"); + throwErrorUnless($obj.cl::get(7) == true, "bool"); + throwErrorUnless($obj.cl::get(9).cell_hash() == $child_obj.cell_hash(), "objRef"); + throwErrorUnless($obj.cl::get(10).cell_hash() == my_dict.cell_hash(), "cellRef"); + throwErrorUnless($obj.cl::get(8).cell_hash() == my_dict.cell_hash(), "dict256"); + + my_dict~udict_set(256, 2, begin_cell().store_uint256(5678).end_cell().begin_parse()); + + cell $new_child_obj = cl::declare( + "child"u, + unsafeTuple([ + [cl::t::uint256, 1] + ]) + ); + + $obj = $obj.cl::set(0, MAX_U8 - 1); + $obj = $obj.cl::set(1, MAX_U16 - 1); + $obj = $obj.cl::set(2, MAX_U32 - 1); + $obj = $obj.cl::set(3, MAX_U64 - 1); + $obj = $obj.cl::set(4, MAX_COINS - 1); + $obj = $obj.cl::set(5, MAX_U256 - 1); + $obj = $obj.cl::set(6, 100); + $obj = $obj.cl::set(7, false); + $obj = $obj.cl::set(8, my_dict); + $obj = $obj.cl::set(9, $new_child_obj); + $obj = $obj.cl::set(10, my_dict); + ;; + throwErrorUnless($obj.cl::get(0) == (MAX_U8 - 1), "set_uint8"); + throwErrorUnless($obj.cl::get(1) == MAX_U16 - 1, "set_uint16"); + throwErrorUnless($obj.cl::get(2) == MAX_U32 - 1, "set_uint32"); + throwErrorUnless($obj.cl::get(3) == MAX_U64 - 1, "set_uint64"); + throwErrorUnless($obj.cl::get(4) == MAX_COINS - 1, "set_coins"); + throwErrorUnless($obj.cl::get(5) == MAX_U256 - 1, "set_uint256"); + throwErrorUnless($obj.cl::get
(6) == 100, "set_address"); + throwErrorUnless($obj.cl::get(7) == false, "set_bool"); + throwErrorUnless($obj.cl::get(8).cell_hash() == my_dict.cell_hash(), "dict256"); + throwErrorUnless($obj.cl::get(9).cell_hash() == $new_child_obj.cell_hash(), "objRef"); + throwErrorUnless($obj.cl::get(10).cell_hash() == my_dict.cell_hash(), "cellRef"); + + return (true, ""); +} + +global int myU8; +global int myU16; +global int myU32; +global int myU64; +global int myCoins; +global int _my256; +global int myAddr; +global int myBool; +global cell myDict; +global cell myCell0; +global cell myCell1; +global cell myCell2; +global cell myCell3; +(int, slice) profileBaseline() impure { + cell test_cell = begin_cell().store_uint256(100).end_cell(); + int start_gas = get_gas_consumed(); + cell myStorage = begin_cell() + .store_uint8(MAX_U8) + .store_uint16(MAX_U16) + .store_uint32(MAX_U32) + .store_uint64(MAX_U64) + .store_coins(MAX_COINS) + .store_uint256(MAX_U256) + .store_uint256(getContractAddress()) + .store_bool(true) + .store_ref(test_cell) + .store_ref( + begin_cell() + .store_ref(test_cell) + .store_ref(test_cell) + .store_ref(test_cell) + .store_ref(test_cell) + .end_cell() + ) + .end_cell(); + slice myStorageSlice = myStorage.begin_parse(); + myU8 = myStorageSlice~load_uint8(); + myU16 = myStorageSlice~load_uint16(); + myU32 = myStorageSlice~load_uint32(); + myU64 = myStorageSlice~load_uint64(); + myCoins = myStorageSlice~load_coins(); + _my256 = myStorageSlice~load_uint256(); + myAddr = myStorageSlice~load_uint256(); + myBool = myStorageSlice~load_bool(); + myDict = myStorageSlice~load_ref(); + cell myNestedCell = myStorageSlice~load_ref(); + slice myNestedCellSlice = myNestedCell.begin_parse(); + myCell0 = myNestedCellSlice~load_ref(); + myCell1 = myNestedCellSlice~load_ref(); + myCell2 = myNestedCellSlice~load_ref(); + myCell3 = myNestedCellSlice~load_ref(); + int end_gas = get_gas_consumed(); + ;; ~dump(myU8); + ;; ~dump(myU16); + ;; ~dump(myU32); + ;; ~dump(myU64); + ;; ~dump(myCoins); + ;; ~dump(_my256); + ;; ~dump(myAddr); + ;; ~dump(myBool); + ;; ~dump(myDict); + ;; ~dump(myCell0); + ;; ~dump(myCell1); + ;; ~dump(myCell2); + ;; ~dump(myCell3); + return (true, ""); +} + +(int, slice) iterateDict256::uint256() impure { + cell dict256Uint256 = cl::dict256::New() + .cl::dict256::set(1, 1) + .cl::dict256::set(2, 2) + .cl::dict256::set(3, 3) + .cl::dict256::set(4, 4); + + (int minKey, int minVal) = dict256Uint256.cl::dict256::getMin(); + throw_unless(1, (minKey == 1) & (minVal == 1)); + (int curKey, int curVal) = dict256Uint256.cl::dict256::getNext(minKey); + throw_unless(1, (curKey == 2) & (curVal == 2)); + (curKey, curVal) = dict256Uint256.cl::dict256::getNext(curKey); + throw_unless(1, (curKey == 3) & (curVal == 3)); + (curKey, curVal) = dict256Uint256.cl::dict256::getNext(curKey); + throw_unless(1, (curKey == 4) & (curVal == 4)); + (curKey, curVal) = dict256Uint256.cl::dict256::getNext(curKey); + throw_unless(1, (curKey == -1) & (curVal.is_null())); + + return (true, ""); +} + +(int, slice) iterateDict256::slice() impure { + cell dict256Uint256 = cl::dict256::New() + .cl::dict256::set(1, begin_cell().store_uint256(1).as_slice()) + .cl::dict256::set(2, begin_cell().store_uint256(2).as_slice()) + .cl::dict256::set(3, begin_cell().store_uint256(3).as_slice()) + .cl::dict256::set(4, begin_cell().store_uint256(4).as_slice()); + + (int minKey, slice minVal) = dict256Uint256.cl::dict256::getMin(); + throw_unless(1, (minKey == 1) & (minVal~load_uint256() == 1)); + (int curKey, slice curVal) = dict256Uint256.cl::dict256::getNext(minKey); + throw_unless(1, (curKey == 2) & (curVal~load_uint256() == 2)); + (curKey, curVal) = dict256Uint256.cl::dict256::getNext(curKey); + throw_unless(1, (curKey == 3) & (curVal~load_uint256() == 3)); + (curKey, curVal) = dict256Uint256.cl::dict256::getNext(curKey); + throw_unless(1, (curKey == 4) & (curVal~load_uint256() == 4)); + (curKey, curVal) = dict256Uint256.cl::dict256::getNext(curKey); + throw_unless(1, (curKey == -1) & (curVal.is_null())); + + return (true, ""); +} + +(int, slice) iterateDict256::cellRef() impure { + cell dict256Uint256 = cl::dict256::New() + .cl::dict256::setRef(1, begin_cell().store_uint256(1).end_cell()) + .cl::dict256::setRef(2, begin_cell().store_uint256(2).end_cell()) + .cl::dict256::setRef(3, begin_cell().store_uint256(3).end_cell()) + .cl::dict256::setRef(4, begin_cell().store_uint256(4).end_cell()); + + (int minKey, cell minVal) = dict256Uint256.cl::dict256::getMin(); + throw_unless(1, (minKey == 1) & (minVal.begin_parse().preload_uint(256) == 1)); + (int curKey, cell curVal) = dict256Uint256.cl::dict256::getNext(minKey); + throw_unless(1, (curKey == 2) & (curVal.begin_parse().preload_uint(256) == 2)); + (curKey, curVal) = dict256Uint256.cl::dict256::getNext(curKey); + throw_unless(1, (curKey == 3) & (curVal.begin_parse().preload_uint(256) == 3)); + (curKey, curVal) = dict256Uint256.cl::dict256::getNext(curKey); + throw_unless(1, (curKey == 4) & (curVal.begin_parse().preload_uint(256) == 4)); + (curKey, curVal) = dict256Uint256.cl::dict256::getNext(curKey); + throw_unless(1, (curKey == -1) & (curVal.is_null())); + + return (true, ""); +} + +(int, slice) iterateDict256::empty() impure { + cell dict256Uint256 = cl::dict256::New(); + + (int minKey, int minUint256Val) = dict256Uint256.cl::dict256::getMin(); + throw_unless(1, (minKey == -1) & (minUint256Val.is_null())); + (int minKey, slice minSliceVal) = dict256Uint256.cl::dict256::getMin(); + throw_unless(1, (minKey == -1) & (minSliceVal.is_null())); + (int minKey, cell minCellVal) = dict256Uint256.cl::dict256::getMin(); + throw_unless(1, (minKey == -1) & (minCellVal.is_null())); + + return (true, ""); +} + +(int, slice) noRefFields::basic() impure { + + cell $path = MOCK_SEND_PATH(); + throw_unless(1, $path.cl::noRefFields()); + + cell $optionsExtended = md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_ENFORCED_OPTIONS_V1() + ); + throw_if(1, $optionsExtended.cl::noRefFields()); + + ;; null objects dont contain refs + throw_unless(1, cl::nullObject().cl::noRefFields()); + + cell $invalid = begin_cell().store_ref(empty_cell()).end_cell(); + throw_if(1, $invalid.cl::noRefFields()); + + return (true, ""); +} + +(int, slice) equalObjTypeShallow::basic() impure { + + ;; same type + cell $a = MOCK_SEND_PATH(); + cell $b = MOCK_RECEIVE_PATH(); + throw_unless(1, $a.cl::equalObjTypeShallow($b)); + + ;; same types SHALLOW... The refs are different, but they are BOTH refs + $a = md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_EXTRA_OPTIONS_V1() + ); + $b = md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_EXTRA_OPTIONS_V2() + ); + throw_unless(1, $a.cl::equalObjTypeShallow($b)); + + ;; same types SHALLOW... The refs are different, but they are BOTH refs + $a = md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_EXTRA_OPTIONS_V2() + ); + $b = md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_EXTRA_OPTIONS_V1() + ); + throw_unless(1, $a.cl::equalObjTypeShallow($b)); + + ;; mismatched types + $a = MOCK_SEND_PATH(); + $b = MOCK_EP_CONFIG(true); + throw_if(1, $a.cl::equalObjTypeShallow($b)); + + ;; same types + $a = MOCK_EP_CONFIG(true); + $b = MOCK_EP_CONFIG(true); + throw_unless(1, $a.cl::equalObjTypeShallow($b)); + + return (true, ""); +} + +(int, slice) sanityBoolCheck::true() impure { + cell storeBool = begin_cell().store_bool(true).end_cell(); + cell storeUint = begin_cell().store_uint(abs(-1), 1).end_cell(); + cell storeIntTrue = begin_cell().store_int(true, 1).end_cell(); + cell storeIntMinusOne = begin_cell().store_int(-1, 1).end_cell(); + + throw_unless(1, + (storeBool.cell_hash() == storeUint.cell_hash()) & + (storeUint.cell_hash() == storeIntTrue.cell_hash()) & + (storeIntTrue.cell_hash() == storeIntMinusOne.cell_hash()) + ); + + return (true, ""); +} + +(int, slice) sanityBoolCheck::false() impure { + cell storeBool = begin_cell().store_bool(false).end_cell(); + cell storeUint = begin_cell().store_uint(0, 1).end_cell(); + cell storeIntFalse = begin_cell().store_int(false, 1).end_cell(); + cell storeIntZero = begin_cell().store_int(0, 1).end_cell(); + + throw_unless(1, + (storeBool.cell_hash() == storeUint.cell_hash()) & + (storeUint.cell_hash() == storeIntFalse.cell_hash()) & + (storeIntFalse.cell_hash() == storeIntZero.cell_hash()) + ); + + return (true, ""); +} + +(int, slice) getStorageFieldL0::uint() impure { + cell $oldStorage = getContractStorage(); + + int id = 10; + cell $lzSend = MOCK_LZ_SEND_WITH_ID(id); + setContractStorage($lzSend); + + int condition = ( + getStorageFieldL0(md::LzSend::sendRequestId) + == id + ); + + setContractStorage($oldStorage); + return test::shouldBeTrue(condition); +} + +(int, slice) getStorageFieldL0::cellRef() impure { + cell $oldStorage = getContractStorage(); + cell $lzSend = MOCK_LZ_SEND_WITH_ID(10); + setContractStorage($lzSend); + + int condition = ( + getStorageFieldL0(md::LzSend::packet).cl::hash() + == MOCK_NONCELESS_PACKET().cl::hash() + ); + + setContractStorage($oldStorage); + return test::shouldBeTrue(condition); +} + +(int, slice) getStorageFieldL1::uint() impure { + cell $oldStorage = getContractStorage(); + cell $packet = MOCK_SEND_PACKET(); + setContractStorage($packet); + + int condition = ( + getStorageFieldL1(lz::Packet::path, lz::Path::srcEid) + == SRC_EID + ); + + setContractStorage($oldStorage); + return test::shouldBeTrue(condition); +} + +(int, slice) getStorageFieldL1::cellRef() impure { + cell $oldStorage = getContractStorage(); + cell $lzSend = MOCK_LZ_SEND_WITH_ID(10); + setContractStorage($lzSend); + + int condition = ( + getStorageFieldL1(md::LzSend::packet, lz::Packet::path).cl::hash() + == MOCK_SEND_PATH().cl::hash() + ); + + setContractStorage($oldStorage); + return test::shouldBeTrue(condition); +} + +;; -------- Dict helpers ----------- +cell _createDictRef(int maxCount) impure { + int count = 0; + cell myDict = cl::dict256::New(); + while (count < maxCount) { + count += 1; + myDict = myDict.cl::dict256::setRef(count, _getRandomCode(count)); + } + return myDict; +} + +cell _createDictUint(int maxCount) impure { + int count = 0; + cell myDict = cl::dict256::New(); + while (count < maxCount) { + count += 1; + myDict = myDict.cl::dict256::set(count, count * 2); + } + return myDict; +} + +cell _createDictNestedRef(int maxCount) impure { + int count = 0; + cell myDict = cl::dict256::New(); + while (count < maxCount) { + count += 1; + myDict = myDict.cl::dict256::setRef(count, _createDictRef((count))); + } + return myDict; +} + +cell _createDictNestedUint(int maxCount) impure { + int count = 0; + cell myDict = cl::dict256::New(); + while (count < maxCount) { + count += 1; + myDict = myDict.cl::dict256::setRef(count, _createDictUint((count))); + } + return myDict; +} + +(int, slice) getStorageFieldL1::dict256::cellRef() impure { + cell $oldStorage = getContractStorage(); + cell dict = _createDictRef(10); + cell $mdObj = md::MdObj::New(dict, empty_cell()); + setContractStorage($mdObj); + + int condition = ( + getStorageFieldL1(md::MdObj::md, 7).cl::hash() + == _getRandomCode(7).cl::hash() + ); + + setContractStorage($oldStorage); + return test::shouldBeTrue(condition); +} + +(int, slice) getStorageFieldL1::dict256::uint() impure { + cell $oldStorage = getContractStorage(); + cell $dict = _createDictUint(10); + cell $mdObj = md::MdObj::New($dict, empty_cell()); + setContractStorage($mdObj); + + int condition = ( + getStorageFieldL1(md::MdObj::md, 7) + == 14 + ); + + setContractStorage($oldStorage); + return test::shouldBeTrue(condition); +} + +(int, slice) getStorageFieldL2::dict256::cellRef() impure { + cell $oldStorage = getContractStorage(); + cell $dict = _createDictNestedRef(10); + cell $mdObj = md::MdObj::New($dict, empty_cell()); + setContractStorage($mdObj); + + int condition = ( + getStorageFieldL2(md::MdObj::md, 7, 5).cl::hash() + == _getRandomCode(5).cl::hash() + ); + + setContractStorage($oldStorage); + return test::shouldBeTrue(condition); +} + +(int, slice) getStorageFieldL2::dict256::uint() impure { + cell $oldStorage = getContractStorage(); + cell $dict = _createDictNestedUint(10); + cell $mdObj = md::MdObj::New($dict, empty_cell()); + setContractStorage($mdObj); + + int condition = ( + getStorageFieldL2(md::MdObj::md, 7, 5) + == 10 + ); + + setContractStorage($oldStorage); + return test::shouldBeTrue(condition); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([get::success::basic, "get::success::basic"]) + .tpush([profileBaseline, "profileBaseline"]) + .tpush([iterateDict256::uint256, "iterateDict256::uint256"]) + .tpush([iterateDict256::slice, "iterateDict256::slice"]) + .tpush([iterateDict256::cellRef, "iterateDict256::cellRef"]) + .tpush([iterateDict256::empty, "iterateDict256::empty"]) + .tpush([noRefFields::basic, "noRefFields:basic"]) + .tpush([equalObjTypeShallow::basic, "equalObjTypeShallow:basic"]) + .tpush([sanityBoolCheck::true, "sanityBoolCheck::true"]) + .tpush([sanityBoolCheck::false, "sanityBoolCheck::false"]) + ;; -- getStorageField Tests + .tpush([getStorageFieldL0::uint, "getStorageFieldL0::uint"]) + .tpush([getStorageFieldL0::cellRef, "getStorageFieldL0::cellRef"]) + .tpush([getStorageFieldL1::uint, "getStorageFieldL1::uint"]) + .tpush([getStorageFieldL1::cellRef, "getStorageFieldL1::cellRef"]) + .tpush([getStorageFieldL1::dict256::cellRef, "getStorageFieldL1::dict256::cellRef"]) + .tpush([getStorageFieldL1::dict256::uint, "getStorageFieldL1::dict256::uint"]) + .tpush([getStorageFieldL2::dict256::cellRef, "getStorageFieldL2::dict256::cellRef"]) + .tpush([getStorageFieldL2::dict256::uint, "getStorageFieldL2::dict256::uint"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/txnContext/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/txnContext/main.fc new file mode 100644 index 00000000..dbe86687 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/txnContext/main.fc @@ -0,0 +1,460 @@ +#include "../../classlib.fc"; +#include "../../constants.fc"; +#include "../../stdlib.fc"; +#include "../../txnContext.fc"; +#include "../../utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../../tests/testInitTxnContextMain.fc"; +#include "../../../apps/counter/interface.fc"; +#include "../../../classes/msgdata/CounterIncrement.fc"; + +;; ================== Constants ================== + +;; Test values +const int TEST_TON_AMOUNT = 1000000000; ;; 1 TON +const int TEST_IHR_FEE = 50000000; ;; 0.05 TON +const int TEST_FWD_FEE = 100000000; ;; 0.1 TON +const int TEST_BALANCE = 0; +const int TEST_MSG_VALUE = 10; +const int TEST_QUERY_ID = 42; +const int TEST_DONATION = 100; + +;; Test address constants +const int TEST_ADDR_WORKCHAIN = -1; +const int TEST_ADDR_PREFIX_VALUE = 2; +const int TEST_ADDR_PREFIX_NO_BOUNCE = 0; + +;; Test name definition +slice _testName() { return "txnContext"; } + +;; Base test preparation +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +;; ================== Helper Functions ================== + +;; Creates error message +slice create_error_message(slice field_name) { + return begin_cell() + .store_slice("txnContext ") + .store_slice(field_name) + .store_slice(" mismatch") + .end_cell() + .begin_parse(); +} + +;; Creates random test address with no bounce +slice create_random_test_address() impure { + ;; Generate random value for address + randomize_lt(); + int address = random(); + + return begin_cell() + .store_uint(TEST_ADDR_PREFIX_VALUE, 2) ;; Standard address prefix (10) + .store_uint(TEST_ADDR_PREFIX_NO_BOUNCE, 1) ;; No bounce + .store_int(TEST_ADDR_WORKCHAIN, 8) ;; Masterchain workchain + .store_uint(address, 256) ;; Random address value + .end_cell() + .begin_parse(); +} + +;; Creates counter metadata +cell create_counter_metadata() impure { + return md::CounterIncrement::New( + DST_EID, + Counter::increment::ab, + MOCK_EXTRA_OPTIONS_V1(), + NATIVE_FEE, + ZRO_FEE + ); +} + +;; ================== Validation Functions ================== + +(int, slice) validate_txn_field(int actual, int expected, slice field_name) impure { + if (actual != expected) { + return ( + TEST_FAILED, + create_error_message(field_name) + ); + } + return (TEST_SUCCESS, ""); +} + +(int, slice) validate_cell_hash(cell actual, cell expected, slice field_name) impure { + ;; Check if both are null; Else check hashes + if(actual.is_null()) { + ifnot(expected.is_null()) { + return ( + TEST_FAILED, + create_error_message(field_name) + ); + } + } else { + if (actual.cell_hash() != expected.cell_hash()) { + return ( + TEST_FAILED, + create_error_message(field_name) + ); + } + } + + return (TEST_SUCCESS, ""); +} + +(int, slice) validate_slice(slice actual, slice expected, slice field_name) impure { + if (actual.slice_hash() != expected.slice_hash()) { + return ( + TEST_FAILED, + create_error_message(field_name) + ); + } + return (TEST_SUCCESS, ""); +} + + +(int, slice) validate_txn_context(int isBounced, int opcode, int queryId, int donationNanos, slice sender_addr, cell in_msg_full, int origin, cell counter_md, slice msgBody) impure { + ;; Validate context length + int ctx_len = txnContext.tlen(); + (int success, slice error) = validate_txn_field(ctx_len, 12, "length"); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate bounce status + (success, error) = validate_txn_field(txnIsBounced(), isBounced, "isBounced"); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate caller + (success, error) = validate_txn_field( + getCaller(), + basechainAddressStdToHashpart(sender_addr), + "caller" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate forward fee + (success, error) = validate_txn_field( + txnContext.int_at(_FWD_FEE), + TEST_FWD_FEE * 3 / 2, + "fwdFee" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate opcode + (success, error) = validate_txn_field( + getOpcode(), + opcode, + "opcode" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate query ID + (success, error) = validate_txn_field( + txnContext.int_at(_QUERY_ID), + queryId, + "queryId" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate balance + (success, error) = validate_txn_field( + getContractBalance(), + TEST_BALANCE, + "balance" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate message value + (success, error) = validate_txn_field( + getMsgValue(), + TEST_MSG_VALUE, + "msgValue" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate msgBody + (success, error) = validate_slice( + txnContext.slice_at(_BODY), + msgBody, + "msgBody" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate raw message + (success, error) = validate_cell_hash( + txnContext.cell_at(_RAW_MSG), + in_msg_full, + "rawMsg" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate origin + (success, error) = validate_txn_field( + getOrigin(), + origin, + "origin" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate donation nanos + (success, error) = validate_txn_field( + getDonationNanos(), + donationNanos, + "donationNanos" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + ;; Validate message data + (success, error) = validate_cell_hash( + getMsgData(), + counter_md, + "md" + ); + if (success != TEST_SUCCESS) { return (success, error); } + + return (TEST_SUCCESS, ""); +} + +;; ================== Message Creation Functions ================== + +(cell, slice) create_message_base(int isBounced, slice sender, slice dest, cell messageBody) impure { + builder msg = begin_cell() + .store_uint(0, 1) ;; tag + .store_uint(1, 1) ;; ihr_disabled + .store_uint(1, 1) ;; allow bounces + .store_uint(isBounced, 1) ;; bounced itself + .store_slice(sender) ;; sender + .store_slice(dest) ;; dest + .store_coins(TEST_TON_AMOUNT) ;; amount + .store_uint(0, 1) ;; extra currencies dict + .store_coins(TEST_IHR_FEE) ;; ihr_fee + .store_coins(TEST_FWD_FEE) ;; fwd_fee + .store_uint(cur_lt(), 64) ;; lt of transaction + .store_uint(now(), 32) ;; unixtime of transaction + .store_uint(0, 1) ;; no init + .store_uint(1, 1) ;; body_in_reference + .store_ref(messageBody); + + cell final_msg = msg.end_cell(); + return (final_msg, messageBody.begin_parse()); +} + +(cell, slice) create_message(int isBounced, slice sender, slice dest, cell body, slice origin_addr) impure { + ;; Creates standard message w/ empty body + if (cell_null?(body)) { + return create_message_base(isBounced, sender, dest, empty_cell()); + } + + ;; Creates message body + builder messageBody = begin_cell(); + + ;; Add bounce code only if bounced + if (isBounced == 1) { + messageBody = messageBody.store_uint(0xFFFFFFFF, 32); + } + + ;; Creates message body + messageBody = messageBody + .store_uint(Counter::OP::INCREMENT, 32) + .store_uint(TEST_QUERY_ID, 64) + .store_coins(TEST_DONATION); + + ;; Override the origin in the body + if (~ slice_empty?(origin_addr)) { + messageBody = messageBody + .store_uint(TEST_ADDR_PREFIX_NO_BOUNCE, 11) + .store_slice(origin_addr); + } + + ;; Store the body ref in the message + cell finalMessageBody = messageBody.store_ref(body).end_cell(); + + return create_message_base( + isBounced, + sender, + dest, + finalMessageBody + ); +} + +;; ================== Test Cases ================== + +(int, slice) initTxnContext::success::basic(cell $storage) impure { + int isBounced = 0; ;; Non-bounced message + slice sender = my_address(); + slice dest = create_random_test_address(); + cell counterMd = create_counter_metadata(); + + (cell inMsgFull, slice inMsgBody) = create_message(isBounced, sender, dest, counterMd, empty_slice()); + initTxnContext(TEST_BALANCE, TEST_MSG_VALUE, inMsgFull, inMsgBody); + + ;; remove opcode, query_id, and donationNanos from the body + int opcode = inMsgBody~load_uint(32); + int queryId = inMsgBody~load_uint(64); + int donationNanos = inMsgBody~load_coins(); + + return validate_txn_context( + FALSE, + opcode, + queryId, + donationNanos, + sender, + inMsgFull, + basechainAddressStdToHashpart(sender), + counterMd, + inMsgBody + ); +} + +(int, slice) initTxnContext::success::origin::overridden(cell $storage) impure { + int isBounced = 0; ;; Non-bounced message + slice sender = my_address(); + slice dest = create_random_test_address(); + cell counterMd = create_counter_metadata(); + slice overrrideOriginAddr = create_random_test_address(); + + (cell inMsgFull, slice inMsgBody) = create_message(isBounced, sender, dest, counterMd, overrrideOriginAddr); + initTxnContext(TEST_BALANCE, TEST_MSG_VALUE, inMsgFull, inMsgBody); + + ;; remove opcode, query_id, and donationNanos from the body + int opcode = inMsgBody~load_uint(32); + int queryId = inMsgBody~load_uint(64); + int donationNanos = inMsgBody~load_coins(); + + return validate_txn_context( + FALSE, + opcode, + queryId, + donationNanos, + sender, + inMsgFull, + sliceToUint256(overrrideOriginAddr), + counterMd, + inMsgBody + ); +} + +(int, slice) initTxnContext::success::inMsgBody::empty(cell $storage) impure { + int isBounced = 0; ;; Non-bounced message + slice sender = my_address(); + slice dest = create_random_test_address(); + + (cell inMsgFull, _) = create_message(isBounced, sender, dest, empty_cell(), empty_slice()); + initTxnContext(TEST_BALANCE, TEST_MSG_VALUE, inMsgFull, empty_slice()); + + int opcode = -1; + int queryId = -1; + int donationNanos = 0; + cell md = null(); + + return validate_txn_context( + FALSE, + opcode, + queryId, + donationNanos, + sender, + inMsgFull, + basechainAddressStdToHashpart(sender), + md, + empty_slice() + ); +} + +(int, slice) initTxnContext::bounced::basic(cell $storage) impure { + int isBounced = 1; ;; Bounced message + slice sender = my_address(); + slice dest = create_random_test_address(); + cell counterMd = create_counter_metadata(); + + (cell inMsgFull, slice inMsgBody) = create_message(isBounced, sender, dest, counterMd, empty_slice()); + initTxnContext(TEST_BALANCE, TEST_MSG_VALUE, inMsgFull, inMsgBody); + + inMsgBody~skip_bits(32); ;; 0xFFFFFFFF + int opcode = inMsgBody~load_uint(32); + int queryId = inMsgBody~load_uint(64); + int donationNanos = inMsgBody~load_coins(); + + return validate_txn_context( + TRUE, + opcode, + queryId, + donationNanos, + sender, + inMsgFull, + basechainAddressStdToHashpart(sender), + counterMd, + inMsgBody + ); +} + +(int, slice) initTxnContext::bounced::origin::overridden(cell $storage) impure { + int isBounced = 1; ;; Bounced message + slice sender = my_address(); + slice dest = create_random_test_address(); + cell counterMd = create_counter_metadata(); + slice overrrideOriginAddr = create_random_test_address(); + + (cell inMsgFull, slice inMsgBody) = create_message(isBounced, sender, dest, counterMd, overrrideOriginAddr); + initTxnContext(TEST_BALANCE, TEST_MSG_VALUE, inMsgFull, inMsgBody); + + inMsgBody~skip_bits(32); ;; 0xFFFFFFFF + int opcode = inMsgBody~load_uint(32); + int queryId = inMsgBody~load_uint(64); + int donationNanos = inMsgBody~load_coins(); + + return validate_txn_context( + TRUE, + opcode, + queryId, + donationNanos, + sender, + inMsgFull, + sliceToUint256(overrrideOriginAddr), + counterMd, + inMsgBody + ); +} + +(int, slice) initTxnContext::bounced::inMsgBody::empty(cell $storage) impure { + int isBounced = 1; ;; Bounced message + slice sender = my_address(); + slice dest = create_random_test_address(); + + (cell inMsgFull, _) = create_message(isBounced, sender, dest, empty_cell(), empty_slice()); + + ;; Create message body with only bounced flag and no other data + slice messageBody = begin_cell().store_uint(0xFFFFFFFF, 32).end_cell().begin_parse(); + + initTxnContext(TEST_BALANCE, TEST_MSG_VALUE, inMsgFull, messageBody); + + int opcode = -1; + int queryId = -1; + int donationNanos = 0; + cell md = null(); + + return validate_txn_context( + TRUE, + opcode, + queryId, + donationNanos, + sender, + inMsgFull, + basechainAddressStdToHashpart(sender), + md, + empty_slice() + ); +} + +;; Test collection +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initTxnContext::success::basic, "initTxnContext::success::basic"]) + .tpush([initTxnContext::success::origin::overridden, "initTxnContext::success::origin::overridden"]) + .tpush([initTxnContext::success::inMsgBody::empty, "initTxnContext::success::inMsgBody::empty"]) + .tpush([initTxnContext::bounced::basic, "initTxnContext::bounced::basic"]) + .tpush([initTxnContext::bounced::origin::overridden, "initTxnContext::bounced::origin::overridden"]) + .tpush([initTxnContext::bounced::inMsgBody::empty, "initTxnContext::bounced::inMsgBody::empty"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/util/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/util/main.fc new file mode 100644 index 00000000..00802986 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/tests/util/main.fc @@ -0,0 +1,141 @@ +#include "../../constants.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../testutils.fc"; +#include "../../../classes/msgdata/LzSend.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "lzUtil"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +;;; ===============================TESTS========================================= +(int, slice) dupWithGarbage::success::Path(cell unused) impure { + cell $path = MOCK_SEND_PATH(); + cell $maliciousPath = _dupWithGarbage($path); + + int cond1 = $path.cl::get(lz::Path::srcEid) + == $maliciousPath.cl::get(lz::Path::srcEid); + int cond2 = $path.cl::hash() != $maliciousPath.cl::hash(); + + $path = $path.cl::set(lz::Path::dstEid, 3); + $maliciousPath = $maliciousPath.cl::set(lz::Path::dstEid, 3); + + int cond3 = $path.cl::get(lz::Path::dstEid) + == $maliciousPath.cl::get(lz::Path::dstEid); + return test::shouldBeTrue(cond1 & cond2 & cond3); +} + +(int, slice) dupWithGarbage::success::Packet(cell unused) impure { + cell $path = MOCK_SEND_PATH(); + cell $packet = lz::Packet::New($path, begin_cell() + .store_uint256("I love messages"c) + .end_cell(), + 1 + ); + cell $maliciousPacket = _dupWithGarbage($packet); + + int cond1 = $packet.cl::hash() != $maliciousPacket.cl::hash(); + int cond2 = $packet.cl::get(lz::Packet::message).cl::hash() + == $maliciousPacket.cl::get(lz::Packet::message).cl::hash(); + + $packet = $packet.cl::set(lz::Packet::message, begin_cell().store_uint256("I love messages even more"c).end_cell()); + $maliciousPacket = $maliciousPacket.cl::set(lz::Packet::message, begin_cell().store_uint256("I love messages even more"c).end_cell()); + + int cond3 = $packet.cl::get(lz::Packet::message).cl::hash() + == $maliciousPacket.cl::get(lz::Packet::message).cl::hash(); + + return test::shouldBeTrue(cond1 & cond2 & cond3); +} + +(int, slice) test::getObjectField::uint(cell unused) impure { + int val = 10; + cell $lzSend = MOCK_LZ_SEND_WITH_ID(val); + return test::shouldBeTrue(cast_to_int(getObjectField($lzSend, md::LzSend::sendRequestId)) == val); +} + +(int, slice) test::getObjectField::ref(cell unused) impure { + cell $lzSend = MOCK_LZ_SEND(); + cell $packet = $lzSend.cl::get(md::LzSend::packet); + return test::shouldBeTrue(cast_to_cell(getObjectField($lzSend, md::LzSend::packet)).cl::hash() == $packet.cl::hash()); +} + +(int, slice) test::getObjectField::bool(cell unused) impure { + cell $epConfig = MOCK_EP_CONFIG(true); + return test::shouldBeTrue( + cast_to_int(getObjectField($epConfig, lz::EpConfig::isNull)) + ); +} + +(int, slice) test::getContractStorageField::uint(cell unused) impure { + int val = 10; + cell $lzSend = MOCK_LZ_SEND_WITH_ID(val); + setContractStorage($lzSend); + return test::shouldBeTrue(cast_to_int(getContractStorageField(md::LzSend::sendRequestId)) == val); +} + +(int, slice) test::getContractStorageField::ref(cell unused) impure { + cell $lzSend = MOCK_LZ_SEND(); + setContractStorage($lzSend); + cell $packet = $lzSend.cl::get(md::LzSend::packet); + return test::shouldBeTrue( + cast_to_cell(getContractStorageField(md::LzSend::packet)).cl::hash() == $packet.cl::hash()); +} + +(int, slice) test::getContractStorageField::bool(cell unused) impure { + cell $epConfig = MOCK_EP_CONFIG(true); + setContractStorage($epConfig); + return test::shouldBeTrue( + cast_to_int(getContractStorageField(lz::EpConfig::isNull)) + ); +} + +(int, slice) test::getContractStorageNestedField::uint(cell unused) impure { + int val = 10; + cell $lzSend = MOCK_LZ_SEND_WITH_ID(val); + cell $mdAddress = md::MdAddress::New($lzSend, NULLADDRESS); + setContractStorage($mdAddress); + return test::shouldBeTrue( + cast_to_int(getContractStorageNestedField(md::MdAddress::md, md::LzSend::sendRequestId)) == val + ); +} + +(int, slice) test::getContractStorageNestedField::ref(cell unused) impure { + int val = 10; + cell $lzSend = MOCK_LZ_SEND_WITH_ID(val); + setContractStorage($lzSend); + return test::shouldBeTrue( + cast_to_cell(getContractStorageNestedField(md::LzSend::packet, lz::Packet::message)).cl::hash() == $lzSend.cl::get(md::LzSend::packet).cl::get(lz::Packet::message).cl::hash()); +} + +(int, slice) test::getContractStorageNestedField::bool(cell unused) impure { + cell $epConfig = MOCK_EP_CONFIG(true); + setContractStorage( + md::MdAddress::New( + $epConfig, + NULLADDRESS + ) + ); + return test::shouldBeTrue( + cast_to_int(getContractStorageNestedField(lz::EpConfig::isNull, lz::EpConfig::isNull)) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([dupWithGarbage::success::Path, "dupWithGarbage::success::Path"]) + .tpush([dupWithGarbage::success::Packet, "dupWithGarbage::success::Packet"]) + .tpush([test::getObjectField::uint, "test::getObjectField::uint"]) + .tpush([test::getObjectField::ref, "test::getObjectField::ref"]) + .tpush([test::getObjectField::bool, "test::getObjectField::bool"]) + .tpush([test::getContractStorageField::uint, "test::getContractStorageField::uint"]) + .tpush([test::getContractStorageField::ref, "test::getContractStorageField::ref"]) + .tpush([test::getContractStorageField::bool, "test::getContractStorageField::bool"]) + .tpush([test::getContractStorageNestedField::uint, "test::getContractStorageNestedField::uint"]) + .tpush([test::getContractStorageNestedField::ref, "test::getContractStorageNestedField::ref"]) + .tpush([test::getContractStorageNestedField::bool, "test::getContractStorageNestedField::bool"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/testutils.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/testutils.fc new file mode 100644 index 00000000..d4d0c71b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/testutils.fc @@ -0,0 +1,98 @@ +#include "utils.fc"; + +cell _garbageFilledCell() { + cell garbage2 = begin_cell() + .store_uint256("garbage2"c) + .end_cell(); + + cell garbage3 = begin_cell() + .store_uint256("garbage3"c) + .end_cell(); + + return begin_cell() + .store_uint256("garbage"c) + .store_ref(garbage2) + .store_ref(garbage3) + .end_cell(); +} + +cell _garbigify(cell $input, cell garbage) impure { + slice inputSlice = $input.begin_parse(); + int numrefs = inputSlice.slice_refs(); + + if (numrefs <= 2) { + builder b = begin_cell().store_slice(inputSlice); + repeat (4 - numrefs) { + b = b.store_ref(garbage); + } + return b.end_cell(); + } elseif (numrefs == 3) { + slice cutfirst = scutfirst(inputSlice, inputSlice.slice_bits(), 2); + builder b = begin_cell().store_slice(cutfirst); + slice s = inputSlice.preload_ref_at(2).begin_parse(); + builder innerB = begin_cell().store_slice(s); + int innerNumRefs = s.slice_refs(); + + repeat (4 - innerNumRefs) { + innerB = innerB.store_ref(garbage); + } + + return b.store_ref(innerB.end_cell()).store_ref(garbage).end_cell(); + } elseif (numrefs == 4) { + builder b = begin_cell().store_slice(scutfirst(inputSlice, inputSlice.slice_bits(), 2)); + slice s1 = inputSlice.preload_ref_at(2).begin_parse(); + slice s2 = inputSlice.preload_ref_at(3).begin_parse(); + builder innerb1 = begin_cell().store_slice(s1); + builder innerb2 = begin_cell().store_slice(s2); + int refs1 = s1.slice_refs(); + int refs2 = s2.slice_refs(); + + repeat (4 - refs1) { + innerb1 = innerb1.store_ref(garbage); + } + + repeat (4 - refs2) { + innerb2 = innerb2.store_ref(garbage); + } + + return b.store_ref(innerb1.end_cell()).store_ref(innerb2.end_cell()).end_cell(); + } + + ;; Should never happen as the above checks and returns from all cases + ;; The compiler needs you to return from all possible control flows + ;; and it isn't smart enough to know that this will never be reached. + throw("garbigify: this print should never happen"c); + return empty_cell(); +} + +() profile_gas(var func, tuple args) impure { + int gas_consumed = get_gas_consumed(); + int gas_consumed_consumed = get_gas_consumed(); + int gas_consumed_of_gas_consumed = gas_consumed_consumed - gas_consumed; + int gas0 = get_gas_consumed(); + var x = func(args); + int gas1 = get_gas_consumed(); + ~strdump("ignore this dump, this is to not optimize out"); + ~dump(x); + int final_gas = gas1 - gas0 - gas_consumed_of_gas_consumed; + ~dump(final_gas); +} + +cell _dupWithGarbage(cell $input) { + return _garbigify($input, _garbageFilledCell()); +} + +cell headerCellGarbage(cell $input) { + int ind = 0; + slice inputSlice = $input.begin_parse(); + builder b = begin_cell(); + while (ind < inputSlice.slice_refs()) { + b = b.store_ref(inputSlice.preload_ref_at(ind)); + ind += 1; + } + b = b.store_slice( + inputSlice + .scutfirst(min(inputSlice.slice_bits(), 512), 0)) + .store_uint256("garbage1"c); + return b.end_cell(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/txnContext.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/txnContext.fc new file mode 100644 index 00000000..ffa11ccd --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/txnContext.fc @@ -0,0 +1,125 @@ +#include "utils.fc"; + +global tuple txnContext; + +const int _IS_BOUNCED = 0; +const int _CALLER = 1; +const int _FWD_FEE = 2; +const int _OPCODE = 3; +const int _QUERY_ID = 4; +const int _BALANCE = 5; +const int _MSG_VALUE = 6; +const int _BODY = 7; +const int _RAW_MSG = 8; +const int _ORIGIN = 9; +const int _DONATION_NANOS = 10; +const int _MD = 11; + +int getMsgValue() impure inline { + return txnContext.int_at(_MSG_VALUE); +} + +int getOpcode() impure inline { + return txnContext.int_at(_OPCODE); +} + +int txnIsBounced() impure inline { + return txnContext.int_at(_IS_BOUNCED); +} + +int getContractBalance() impure inline { + return txnContext.int_at(_BALANCE); +} + +int getInitialContractBalance() impure inline { + return getContractBalance() - getMsgValue(); +} + +int getCaller() impure inline { + return txnContext.int_at(_CALLER); +} + +int getOrigin() impure inline { + return txnContext.int_at(_ORIGIN); +} + +slice getOriginStd() impure inline { + return hashpartToBasechainAddressStd(getOrigin()); +} + +int getDonationNanos() impure inline { + return txnContext.int_at(_DONATION_NANOS); +} + +() setDonationNanos(int nanos) impure inline { + txnContext~tset(_DONATION_NANOS, nanos); +} + +cell getMsgData() impure inline { + return txnContext.cell_at(_MD); +} + +() setOrigin(int newOrigin) impure inline { + txnContext~tset(_ORIGIN, newOrigin); +} + +;; returns if slice empty +;; if empty body, sets opcode=-1 & query_id=-1, so it cannot be faked +() initTxnContext(int myBalance, int msgValue, cell inMsgFull, slice inMsgBody) impure inline { + slice cs = inMsgFull.begin_parse(); + int flags = cs~load_uint(4); + + int _is_bounced = false; + if flags & 1 { + _is_bounced = true; + inMsgBody~skip_bits(32); ;; 0xFFFFFFFF + } + + int opcode = -1; + int query_id = -1; + int donationNanos = 0; + cell md = null(); + + slice _sender_address = cs~load_msg_addr(); + cs~load_msg_addr(); + cs~load_coins(); + cs~skip_dict(); + cs~load_coins(); + int senderAddress = basechainAddressStdToHashpart(_sender_address); + + ;; by default, the origin is the sender address + int origin = senderAddress; + + ;; the inMsgBody parsing is technically compatible with the reference jetton implementation + ;; where donationNanos == the amount of tokens received + ;; and and the origin will contain garbage data + ifnot (inMsgBody.slice_empty?()) { + opcode = inMsgBody~load_uint(32); + query_id = inMsgBody~load_uint(64); + donationNanos = inMsgBody~load_coins(); + ;; if the origin is explicitly overriden in the body, use that + if (inMsgBody.slice_bits() >= 267) { + origin = inMsgBody.preload_bits_offset(11, 256).preload_uint(256); + } + ifnot (inMsgBody.slice_refs_empty?()) { + md = inMsgBody.preload_ref(); + } + } + + txnContext = castToTuple([ + _is_bounced, + senderAddress, + muldiv(cs~load_coins(), 3, 2), + opcode, + query_id, + myBalance, + msgValue, + inMsgBody, ;; could be an empty slice + inMsgFull, + origin, + donationNanos, + md + ]); +} + +(builder) beginTonMessage(int _opcode) asm "txnContext GETGLOB 4 INDEX SWAP NEWC 32 STU 64 STU"; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/utils.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/utils.fc new file mode 100644 index 00000000..c1579b09 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/funC++/utils.fc @@ -0,0 +1,323 @@ +#include "constants.fc"; +#include "stdlib.fc"; + +const int ERROR::WrongWorkchain = 2047; + +forall X -> tuple unsafeTuple(X x) asm "NOP"; +(slice) as_slice(builder b) asm "ENDC CTOS"; +(slice, int) load_uint8(slice s) asm "8 LDU SWAP"; +(builder) store_uint8(builder b, int t) inline asm(t b) "8 STU"; +(slice, int) load_uint16(slice s) asm "16 LDU SWAP"; +(builder) store_uint16(builder b, int t) inline asm(t b) "16 STU"; +(slice, int) load_uint32(slice s) asm "32 LDU SWAP"; +(builder) store_uint32(builder b, int t) inline asm(t b) "32 STU"; +(slice, int) load_uint64(slice s) asm "64 LDU SWAP"; +(builder) store_uint64(builder b, int t) inline asm(t b) "64 STU"; +(slice, int) load_uint128(slice s) asm "128 LDU SWAP"; +(builder) store_uint128(builder b, int t) inline asm(t b) "128 STU"; +(slice, int) load_uint256(slice s) asm "256 LDU SWAP"; +(builder) store_uint256(builder b, int t) inline asm(t b) "256 STU"; +forall X -> int is_null(X x) asm "ISNULL"; +forall X -> int is_int(X x) asm "<{ TRY:<{ 0 PUSHINT ADD DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS"; +forall X -> int is_cell(X x) asm "<{ TRY:<{ CTOS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS"; +forall X -> int is_slice(X x) asm "<{ TRY:<{ SBITS DROP -1 PUSHINT }>CATCH<{ 2DROP 0 PUSHINT }> }>CONT 1 1 CALLXARGS"; +forall X -> int is_tuple(X x) asm "ISTUPLE"; +forall X -> cell cast_to_cell(X x) asm "NOP"; +forall X -> slice cast_to_slice(X x) asm "NOP"; +forall X -> int cast_to_int(X x) asm "NOP"; +forall X -> tuple cast_to_tuple(X x) asm "NOP"; +(cell) my_code() asm "MYCODE"; +(tuple) get_values() asm "INCOMINGVALUE"; +int storage_fees() asm "STORAGEFEES"; +(int, slice) ldones(slice s) asm "LDONES"; + +(int) get_gas_consumed() asm "GASCONSUMED"; + +builder store_zeroes(builder b, int x) asm "STZEROES"; +builder store_ones(builder b, int x) asm "STONES"; +cell preload_first_ref(slice s) asm "0 PLDREFIDX"; +slice preload_bits_offset(slice s, int offset, int len) asm "SDSUBSTR"; +(slice, int) load_bool(slice s) asm(-> 1 0) "1 LDI"; +int preload_bool(slice s) asm "1 PUSHINT PLDIX"; +(builder) store_bool(builder b, int v) asm(v b) "1 STI"; +cell empty_cell() asm " PUSHREF"; +forall X -> tuple tset(tuple t, int k, X x) asm(t x k) "SETINDEXVAR"; +forall X -> (tuple, ()) ~tset(tuple t, int k, X x) asm(t x k) "SETINDEXVAR"; +forall X -> (tuple, X) tpop(tuple t) asm "TPOP"; +int tlen(tuple t) asm "TLEN"; +int keccak256Builder(builder b) asm "1 PUSHINT HASHEXT_KECCAK256"; + +int cell_is_empty(cell c) impure inline { + return c.cell_hash() == 68134197439415885698044414435951397869210496020759160419881882418413283430343; +} + +int get_compute_fee(int workchain, int gas_used) asm(gas_used workchain) "GETGASFEE"; +int get_storage_fee(int workchain, int seconds, int bits, int cells) asm(cells bits seconds workchain) "GETSTORAGEFEE"; +int get_forward_fee(int workchain, int bits, int cells) asm(cells bits workchain) "GETFORWARDFEE"; + +int ilog4(int x) asm(x) "UBITSIZE 4 PUSHINT DIV"; +cell preload_ref_at(slice s, int idx) inline asm "PLDREFVAR"; +slice scutfirst(slice s, int bits, int refs) inline asm "SCUTFIRST"; +slice scutlast(slice s, int bits, int refs) inline asm "SCUTLAST"; +slice subslice(slice s, int start_bits, int start_refs, int bits, int refs) inline asm "SUBSLICE"; +slice sskipfirst(slice s, int bits, int refs) inline asm "SSKIPFIRST"; +slice sskiplast(slice s, int bits, int refs) inline asm "SSKIPLAST"; +slice sdskipfirst(slice s, int bits) inline asm "SDSKIPFIRST"; +forall X -> builder cast_to_builder(X x) inline asm "NOP"; +int abs(int x) inline asm "ABS"; +tuple self_balance() asm "BALANCE"; + +() throwError(slice reason) impure inline { + ~strdump(reason); + throw(reason.slice_hash() & ERRORCODE_MASK); +} + +() throwErrorUnless(int condition, slice reason) impure inline { + ifnot (condition) { + throwError(reason); + } +} +int _SDCNTLEAD0(slice x) asm "SDCNTLEAD0"; +int POW2(int y) asm "POW2"; + +;; numCells, num_bits +(int, int) getContractStateSize(cell code, cell init_storage) impure inline { + cell stateInit = begin_cell() + .store_uint(6, 5) + .store_ref(code) + .store_ref(init_storage) + .end_cell(); + (int cellsCount, int bitsCount, int success) = stateInit.compute_data_size(MAX_U16); + throw_unless(8, success); + return (cellsCount, bitsCount); +} + +int calculateStorageFees(int cellsCount, int bitsCount, int timeDelta) impure inline { + return get_storage_fee(BASECHAIN, timeDelta, bitsCount, cellsCount); +} + +forall X -> tuple castToTuple(X x) asm "NOP"; + +slice empty_slice() asm " = 592) { + return (-1, -1, -1, -1, -1, -1, -1, -1, -1); + } + + if ( + (cfgSlice.preload_uint(8) != 0xd1) + | (cfgSlice.preload_bits_offset(136, 8).preload_uint(8) != 0xde) + ) { + return (-1, -1, -1, -1, -1, -1, -1, -1, -1); + } + + cfgSlice~load_uint8(); + int specialGasLimit = cfgSlice~load_uint64(); + int flatGasLimit = cfgSlice~load_uint64(); + int flatGasPrice = cfgSlice~load_uint64(); + cfgSlice~load_uint8(); + int gasPrice = cfgSlice~load_uint64(); + int gasLimit = cfgSlice~load_uint64(); + int gasCredit = cfgSlice~load_uint64(); + int blockGasLimit = cfgSlice~load_uint64(); + int freezeDueLimit = cfgSlice~load_uint64(); + int deleteDueLimit = cfgSlice~load_uint64(); + return ( + specialGasLimit, + flatGasLimit, + flatGasPrice, + gasPrice, + gasLimit, + gasCredit, + blockGasLimit, + freezeDueLimit, + deleteDueLimit + ); +} + +;;; ====================== Address functions ====================== +int basechainAddressStdToHashpart(slice full_address) impure inline { + (int wc, int hp) = parseStdAddress(full_address); + throw_if(ERROR::WrongWorkchain, wc != BASECHAIN); + return hp; +} + +slice hashpartToBasechainAddressStd(int hashpart) impure inline { + return begin_cell() + .store_uint(4, 3) ;; 0b100 + .store_int(BASECHAIN, 8) + .store_uint(hashpart, 256) + .as_slice(); +} + +int getContractAddress() impure inline { + return my_address().preload_bits_offset(11, 256).preload_uint(256); +} + +() setContractStorage(cell $obj) impure inline { + set_data($obj); +} + +cell getContractStorage() impure inline method_id { + return get_data(); +} + +int getContractBalanceView(int futureSeconds) impure inline method_id { + (int cellsCount, int bitsCount) = getContractStateSize(my_code(), getContractStorage()); + + int ret = self_balance().int_at(0) - calculateStorageFees(cellsCount, bitsCount, futureSeconds); + + return max(0, ret); +} + +int computeContractAddress(cell $storage, cell code) impure inline { + return begin_cell() + .store_uint(6, 5) + .store_ref(code) + .store_ref($storage) + .end_cell() + .cell_hash(); +} + +;; ============================== Optimization Functions ============================== + +;; ========================== For Slices ========================== + +int preloadBoolAt(slice self, int offset) impure inline { + ;; bools should be returned as bools + return self.preload_bits_offset(offset, 1).preload_bool(); +} + +int preloadUint8At(slice self, int offset) impure inline { + return self.preload_bits_offset(offset, 8).preload_uint(8); +} + +int preloadUint16At(slice self, int offset) impure inline { + return self.preload_bits_offset(offset, 16).preload_uint(16); +} + +int preloadUint32At(slice self, int offset) impure inline { + return self.preload_bits_offset(offset, 32).preload_uint(32); +} + +int preloadUint64At(slice self, int offset) impure inline { + return self.preload_bits_offset(offset, 64).preload_uint(64); +} + +int preloadCoinsAt(slice self, int offset) impure inline { + return self.preload_bits_offset(offset, 128).preload_uint(128); +} + +int preloadUint256At(slice self, int offset) impure inline { + return self.preload_bits_offset(offset, 256).preload_uint(256); +} + +int preloadAddressAt(slice self, int offset) impure inline { + return self.preloadUint256At(offset); +} + +;; slice -> cell +cell preloadRefAt(slice self, int offset) impure inline { + return self.preload_ref_at(offset); +} + +;; slice -> slice +slice preloadRefSliceAt(slice self, int offset) impure inline { + return self.preload_ref_at(offset).begin_parse(); +} + +;; ========================== For Cells ========================== + +int cellPreloadBoolAt(cell self, int offset) impure inline { + return self.begin_parse().preloadBoolAt(offset); +} + +int cellPreloadUint8At(cell self, int offset) impure inline { + return self.begin_parse().preloadUint8At(offset); +} + +int cellPreloadUint16At(cell self, int offset) impure inline { + return self.begin_parse().preloadUint16At(offset); +} + +int cellPreloadUint32At(cell self, int offset) impure inline { + return self.begin_parse().preloadUint32At(offset); +} + +int cellPreloadUint64At(cell self, int offset) impure inline { + return self.begin_parse().preloadUint64At(offset); +} + +int cellPreloadCoinsAt(cell self, int offset) impure inline { + return self.begin_parse().preloadCoinsAt(offset); +} + +int cellPreloadUint256At(cell self, int offset) impure inline { + return self.begin_parse().preloadUint256At(offset); +} + +int cellPreloadAddressAt(cell self, int offset) impure inline { + return self.cellPreloadUint256At(offset); +} + +;; cell -> cell +cell cellPreloadRefAt(cell self, int offset) impure inline { + return self.begin_parse().preloadRefAt(offset); +} + +;; cell -> slice +slice cellPreloadRefSliceAt(cell self, int offset) impure inline { + return self.begin_parse().preloadRefAt(offset).begin_parse(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/meta.json b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/meta.json new file mode 100644 index 00000000..d21a5fde --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/meta.json @@ -0,0 +1,7 @@ +{ + "name": "Token", + "description": "token", + "symbol": "TOKEN", + "decimals": 6, + "image": "" +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/minter.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/minter.fc new file mode 100644 index 00000000..91d7a3f4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/minter.fc @@ -0,0 +1,120 @@ +#include "../../../node_modules/@ston-fi/funcbox/autoload.fc"; +#include "utils.fc"; +#include "op-codes.fc"; +#include "../../../node_modules/@ston-fi/funcbox/contracts/stdlib.fc"; +;; It is recommended to use https://github.com/ton-blockchain/token-contract/blob/main/ft/jetton-minter-discoverable.fc +;; instead of this contract, see https://github.com/ton-blockchain/TEPs/blob/master/text/0089-jetton-wallet-discovery.md + +;; Jettons minter smart contract + +;; storage scheme +;; storage#_ total_supply:Coins admin_address:MsgAddress content:^Cell jetton_wallet_code:^Cell = Storage; + +(int, slice, cell, cell) load_data() inline { + slice ds = get_data().begin_parse(); + return ( + ds~load_coins(), ;; total_supply + ds~load_msg_addr(), ;; admin_address + ds~load_ref(), ;; content + ds~load_ref() ;; jetton_wallet_code + ); +} + +() save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline { + set_data(begin_cell() + .store_coins(total_supply) + .store_slice(admin_address) + .store_ref(content) + .store_ref(jetton_wallet_code) + .end_cell() + ); +} + +() mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) impure { + cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code); + slice to_wallet_address = calculate_jetton_wallet_address(state_init); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(to_wallet_address) + .store_coins(amount) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(master_msg); + send_raw_message(msg.end_cell(), 64); ;; pay transfer fees separately, revert on errors +} + +() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + int op = in_msg_body~load_uint(32); + int query_id = in_msg_body~load_uint(64); + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + if (op == op::mint()) { + throw_unless(73, equal_slices(sender_address, admin_address)); + + slice to_address = in_msg_body~load_msg_addr(); + int amount = in_msg_body~load_coins(); + cell master_msg = in_msg_body~load_ref(); + slice master_msg_cs = master_msg.begin_parse(); + master_msg_cs~skip_bits(32 + 64); ;; op + query_id + int jetton_amount = master_msg_cs~load_coins(); + mint_tokens(to_address, jetton_wallet_code, amount, master_msg); + save_data(total_supply + jetton_amount, to_address, content, jetton_wallet_code); + return (); + } + + if (op == op::burn_notification()) { + int jetton_amount = in_msg_body~load_coins(); + slice from_address = in_msg_body~load_msg_addr(); + throw_unless(74, + equal_slices(calculate_user_jetton_wallet_address(from_address, my_address(), jetton_wallet_code), sender_address) + ); + save_data(total_supply - jetton_amount, admin_address, content, jetton_wallet_code); + slice response_address = in_msg_body~load_msg_addr(); + if (response_address.preload_uint(2) != 0) { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 + .store_slice(response_address) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::excesses, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 2 + 64); + } + return (); + } + + if (op == 3) { ;; change admin + throw_unless(73, equal_slices(sender_address, admin_address)); + slice new_admin_address = in_msg_body~load_msg_addr(); + save_data(total_supply, new_admin_address, content, jetton_wallet_code); + return (); + } + + if (op == 4) { ;; change content, delete this for immutable tokens + throw_unless(73, equal_slices(sender_address, admin_address)); + save_data(total_supply, admin_address, in_msg_body~load_ref(), jetton_wallet_code); + return (); + } + + throw(0xffff); +} + +(int, int, slice, cell, cell) get_jetton_data() method_id { + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + return (total_supply, -1, admin_address, content, jetton_wallet_code); +} + +slice get_wallet_address(slice owner_address) method_id { + (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data(); + return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/op-codes.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/op-codes.fc new file mode 100644 index 00000000..a5b67ced --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/op-codes.fc @@ -0,0 +1,10 @@ +int op::transfer() asm "0xf8a7ea5 PUSHINT"; +int op::transfer_notification() asm "0x7362d09c PUSHINT"; +int op::internal_transfer() asm "0x178d4519 PUSHINT"; +;; this is "copied" because it's already imported in funcbox +int op::excesses_copy() asm "0xd53276db PUSHINT"; +int op::burn() asm "0x595f07bc PUSHINT"; +int op::burn_notification() asm "0x7bdd97de PUSHINT"; + +;; Minter +int op::mint() asm "0x642b7d07 PUSHINT"; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/params.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/params.fc new file mode 100644 index 00000000..0f184c3c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/params.fc @@ -0,0 +1,18 @@ +#include "../../../node_modules/@ston-fi/funcbox/autoload.fc"; + +int workchain() asm "0 PUSHINT"; + +;; () force_chain(slice addr) impure { +;; (int wc, _) = parse_std_addr(addr); +;; throw_unless(333, wc == workchain()); +;; } + +(int) get_workchain(slice address) inline { + (int wc, _) = parse_std_addr(address); + return wc; +} + +() force_chain(int workchain, slice address, int error_code) impure inline { + (int wc) = get_workchain(address); + throw_unless(error_code, wc == workchain); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/utils.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/utils.fc new file mode 100644 index 00000000..de6eaa21 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/utils.fc @@ -0,0 +1,33 @@ +#include "../../../node_modules/@ston-fi/funcbox/autoload.fc"; +#include "params.fc"; + +cell pack_jetton_wallet_data(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_coins(balance) + .store_slice(owner_address) + .store_slice(jetton_master_address) + .store_ref(jetton_wallet_code) + .end_cell(); +} + +cell calculate_jetton_wallet_state_init(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_uint(0, 2) + .store_dict(jetton_wallet_code) + .store_dict(pack_jetton_wallet_data(0, owner_address, jetton_master_address, jetton_wallet_code)) + .store_uint(0, 1) + .end_cell(); +} + +slice calculate_jetton_wallet_address(cell state_init) inline { + return begin_cell().store_uint(4, 3) + .store_int(workchain(), 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +slice calculate_user_jetton_wallet_address(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return calculate_jetton_wallet_address(calculate_jetton_wallet_state_init(owner_address, jetton_master_address, jetton_wallet_code)); +} + diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/wallet.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/wallet.fc new file mode 100644 index 00000000..4f382edb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/jettons/zro/wallet.fc @@ -0,0 +1,261 @@ +#include "op-codes.fc"; +#include "utils.fc"; +#include "../../../node_modules/@ston-fi/funcbox/autoload.fc"; + +;; Jetton Wallet Smart Contract + +{- + +NOTE that this tokens can be transferred within the same workchain. + +This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions: + +1) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`) + +2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain) + +-} + +int min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON +;; Note that 2 * gas_consumptions is expected to be able to cover fees on both wallets (sender and receiver) +;; and also constant fees on inter-wallet interaction, in particular fwd fee on state_init transfer +;; that means that you need to reconsider this fee when: +;; a) jetton logic become more gas-heavy +;; b) jetton-wallet code (sent with inter-wallet message) become larger or smaller +;; c) global fee changes / different workchain +int gas_consumption() asm "15000000 PUSHINT"; ;; 0.015 TON + +{- + Storage + storage#_ balance:Coins owner_address:MsgAddressInt jetton_master_address:MsgAddressInt jetton_wallet_code:^Cell = Storage; +-} + +(int, slice, slice, cell) load_data() inline { + slice ds = get_data().begin_parse(); + return (ds~load_coins(), ds~load_msg_addr(), ds~load_msg_addr(), ds~load_ref()); +} + +() save_data (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) impure inline { + set_data(pack_jetton_wallet_data(balance, owner_address, jetton_master_address, jetton_wallet_code)); +} + +{- + transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress + response_destination:MsgAddress custom_payload:(Maybe ^Cell) + forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) + = InternalMsgBody; + internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress + response_address:MsgAddress + forward_ton_amount:(VarUInteger 16) + forward_payload:(Either Cell ^Cell) + = InternalMsgBody; +-} + +() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + slice to_owner_address = in_msg_body~load_msg_addr(); + force_chain(BASECHAIN, to_owner_address, 101); + + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + balance -= jetton_amount; + + throw_unless(705, equal_slices(owner_address, sender_address)); + throw_unless(706, balance >= 0); + + cell state_init = calculate_jetton_wallet_state_init(to_owner_address, jetton_master_address, jetton_wallet_code); + slice to_wallet_address = calculate_jetton_wallet_address(state_init); + slice response_address = in_msg_body~load_msg_addr(); + cell custom_payload = in_msg_body~load_dict(); + int forward_ton_amount = in_msg_body~load_coins(); + throw_unless(708, slice_bits(in_msg_body) >= 1); + slice either_forward_payload = in_msg_body; + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(to_wallet_address) + .store_coins(0) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init); + var msg_body = begin_cell() + .store_uint(op::internal_transfer(), 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(owner_address) + .store_slice(response_address) + .store_coins(forward_ton_amount) + .store_slice(either_forward_payload) + .end_cell(); + + msg = msg.store_ref(msg_body); + int fwd_count = forward_ton_amount ? 2 : 1; + throw_unless(709, msg_value > + forward_ton_amount + + ;; 3 messages: wal1->wal2, wal2->owner, wal2->response + ;; but last one is optional (it is ok if it fails) + fwd_count * fwd_fee + + (2 * gas_consumption() + min_tons_for_storage())); + ;; universal message send fee calculation may be activated here + ;; by using this instead of fwd_fee + ;; msg_fwd_fee(to_wallet, msg_body, state_init, 15) + + send_raw_message(msg.end_cell(), 64); ;; revert on errors + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +{- + internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress + response_address:MsgAddress + forward_ton_amount:(VarUInteger 16) + forward_payload:(Either Cell ^Cell) + = InternalMsgBody; +-} + +() receive_tokens (slice in_msg_body, slice sender_address, int my_ton_balance, int fwd_fee, int msg_value) impure { + ;; NOTE we can not allow fails in action phase since in that case there will be + ;; no bounce. Thus check and throw in computation phase. + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + balance += jetton_amount; + slice from_address = in_msg_body~load_msg_addr(); + slice response_address = in_msg_body~load_msg_addr(); + throw_unless(707, + equal_slices(jetton_master_address, sender_address) + | + equal_slices(calculate_user_jetton_wallet_address(from_address, jetton_master_address, jetton_wallet_code), sender_address) + ); + int forward_ton_amount = in_msg_body~load_coins(); + + int ton_balance_before_msg = my_ton_balance - msg_value; + int storage_fee = min_tons_for_storage() - min(ton_balance_before_msg, min_tons_for_storage()); + msg_value -= (storage_fee + gas_consumption()); + slice either_forward_payload = in_msg_body; + if(forward_ton_amount) { + msg_value -= (forward_ton_amount + fwd_fee); + + var msg_body = begin_cell() + .store_uint(op::transfer_notification(), 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(from_address) + .store_slice(either_forward_payload) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) ;; we should not bounce here cause receiver can have uninitialized contract + .store_slice(owner_address) + .store_coins(forward_ton_amount) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(msg_body); + + send_raw_message(msg.end_cell(), 1); + } + + if ((response_address.preload_uint(2) != 0) & (msg_value > 0)) { + + var msg_body = begin_cell() + .store_uint(op::excesses, 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(from_address) + .store_slice(either_forward_payload) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000 + .store_slice(response_address) + .store_coins(msg_value) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(msg_body); + send_raw_message(msg.end_cell(), 2); + } + + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() burn_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { + ;; NOTE we can not allow fails in action phase since in that case there will be + ;; no bounce. Thus check and throw in computation phase. + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + slice response_address = in_msg_body~load_msg_addr(); + ;; ignore custom payload + ;; slice custom_payload = in_msg_body~load_dict(); + balance -= jetton_amount; + throw_unless(705, equal_slices(owner_address, sender_address)); + throw_unless(706, balance >= 0); + throw_unless(707, msg_value > fwd_fee + 2 * gas_consumption()); + + var msg_body = begin_cell() + .store_uint(op::burn_notification(), 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(owner_address) + .store_slice(response_address) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(jetton_master_address) + .store_coins(0) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(msg_body); + + send_raw_message(msg.end_cell(), 64); + + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() on_bounce (slice in_msg_body) impure { + in_msg_body~skip_bits(32); ;; 0xFFFFFFFF + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int op = in_msg_body~load_uint(32); + throw_unless(709, (op == op::internal_transfer()) | (op == op::burn_notification())); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + balance += jetton_amount; + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + if (flags & 1) { + on_bounce(in_msg_body); + return (); + } + slice sender_address = cs~load_msg_addr(); + cs~load_msg_addr(); ;; skip dst + cs~load_coins(); ;; skip value + cs~skip_bits(1); ;; skip extracurrency collection + cs~load_coins(); ;; skip ihr_fee + int fwd_fee = muldiv(cs~load_coins(), 3, 2); ;; we use message fwd_fee for estimation of forward_payload costs + + int op = in_msg_body~load_uint(32); + + if (op == op::transfer()) { ;; outgoing transfer + send_tokens(in_msg_body, sender_address, msg_value, fwd_fee); + return (); + } + + if (op == op::internal_transfer()) { ;; incoming transfer + receive_tokens(in_msg_body, sender_address, my_balance, fwd_fee, msg_value); + return (); + } + + if (op == op::burn()) { ;; burn + burn_tokens(in_msg_body, sender_address, msg_value, fwd_fee); + return (); + } + + throw(0xffff); +} + +(int, slice, slice, cell) get_wallet_data() method_id { + return load_data(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/auto/order_code.func b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/auto/order_code.func new file mode 100644 index 00000000..5414d0bd --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/auto/order_code.func @@ -0,0 +1,7 @@ + +;; https://docs.ton.org/tvm.pdf, page 30 +;; Library reference cell — Always has level 0, and contains 8+256 data bits, including its 8-bit type integer 2 +;; and the representation hash Hash(c) of the library cell being referred to. When loaded, a library +;; reference cell may be transparently replaced by the cell it refers to, if found in the current library context. + +cell order_code() asm "spec PUSHREF"; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/bocs/MultiSig.compiled.json b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/bocs/MultiSig.compiled.json new file mode 100644 index 00000000..49947390 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/bocs/MultiSig.compiled.json @@ -0,0 +1,5 @@ +{ + "hash": "1a9652b3a16a6fd12a1df3d44a9d24516bb82ae00306e758be7b53f8fa726261", + "hashBase64": "GpZSs6Fqb9EqHfPUSp0kUWu4KuADBudYvntT+PpyYmE=", + "hex": "b5ee9c7241021201000495000114ff00f4a413f4bcf2c80b01020162020b02cad033d0d3030171b0925f03e0fa403022d749c000925f03e002d31f0120c000925f04e001d33f01ed44d0d3ff0101d3070101d4d3070101f404d2000101d1288210f718510fbae30f054443c8500601cbff500401cb0712cc0101cb07f4000101ca00c9ed540307019e3806d3ff0128b38e122084ffba923024965305baf2e3f0e205a405de01d2000101d3070101d32f0101d4d1239126912ae2523078f40e6fa1f2e3ef1ec705f2e3ef20f823bef2e06f20f823a1546d700401d4f80703830cf94130038308f94130f8075006a18127f801a070f83681120670f836a0812bec70f836a0811d9870f836a022a60622a081053926a027a070f83823a481029827a070f838a003a60658a08106e05005a05005a0430370f83759a001a01cbef2e064f82850030502b8017002c858cf160101cbffc98822c8cb01f400f400cb00c97021f90074c8cb0212ca07cbffc9d0c882109c73fba2580a02cb1fcb3f2601cb075250cc500b01cb2f1bcc2a01ca000a951901cb07089130e2102470408980188050db3c110600928e45c85801cb055005cf165003fa0254712323ed44ed45ed479f5bc85003cf17c913775003cb6bcccced67ed65ed64747fed11987601cb6bcc01cf17ed41edf101f2ffc901fb00db06029a363826821075097f5dba8eba068210a32c59bfba8ea9f82818c705f2e06503d4d1103410364650f8007f8e8d2178f47c6fa5209132e30d01b3e65b10355034923436e2505413e30d4015503304090802e23604d3ff0101d32f0101d3070101d3ff0101d4d1f8285005017002c858cf160101cbffc98822c8cb01f400f400cb00c97001f90074c8cb0212ca07cbffc9d01bc705f2e06526f9001aba5193be19b0f2e06607f823bef2e06f44145056f8007f8e8d2178f47c6fa5209132e30d01b3e65b110901fa02d74cd0d31f01208210f1381e5bba8e6a82101d0cfbd3ba8e5e6c44d3070101d4217f708e17511278f47c6fa53221995302baf2e06702a402de01b312e66c2120c200f2e06e23c200f2e06d5330bbf2e06d01f404217f708e17511278f47c6fa53221995302baf2e06702a402de01b312e66c2130d155239130e2e30d0a001030d307d402fb00d10201200c0e0143bf74ff6a26869ff8080e9838080ea69838080fa0269000080e8881aaf8280fc11d0c0d00c2f80703830cf94130038308f94130f8075006a18127f801a070f83681120670f836a0812bec70f836a0811d9870f836a022a60622a081053926a027a070f83823a481029827a070f838a003a60658a08106e05005a05005a0430370f83759a001a00201660f1000f1b0cafb513434ffc04074c1c0407534c1c0407d01348000407448dfdc2385d4449e3d1f1be94c886654c0aebcb819c0a900b7806cc4b99b08548c2ebcb81b085fdc2385d4449e3d1f1be94c886654c0aebcb819c0a900b7806cc4b99b084c08b0803cb81b8930803cb81b5490eefcb81b40648cdfe440f880e00159b0c9fe0a00405c00b21633c5804072fff26208b232c07d003d0032c0325c007e401d3232c084b281f2fff27420110842024ebdec6e60259521d325e0baebe89b9a7901619805a4dfa624e5a3549349fcbf3e76e893" +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/bocs/MultiSigOrder.compiled.json b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/bocs/MultiSigOrder.compiled.json new file mode 100644 index 00000000..3a8e2e6e --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/bocs/MultiSigOrder.compiled.json @@ -0,0 +1,5 @@ +{ + "hash": "4ebdec6e60259521d325e0baebe89b9a7901619805a4dfa624e5a3549349fcbf", + "hashBase64": "Tr3sbmAllSHTJeC66+ibmnkBYZgFpN+mJOWjVJNJ/L8=", + "hex": "b5ee9c7241020d01000372000114ff00f4a413f4bcf2c80b01020162020c04f8d03331d0d3030171b0915be0fa403001d31f01ed44d0fa4001f861d3ff01f86220d749c0008e13306df8636df8646df8656df8666df8676df8688e22d30701f863d20001f864d401f865d3ff01f866d30701f867d32f01f868d401f869d1e220c000e30201d33f012282109c73fba2bae302028210a762230fbae3020304060b01c83020d74ac0008e23c8708e1a22d7495230d71912cf1622d74a9402d74cd093317f58e2541220e63031c9d0df840f018b7617070726f76658c705f2f420707f8e19f84578f47c6fa5209b5243c70595317f327001de9132e201b3e632f2e06af82512db3c0702fe32f84113c705f2e068f8436e8ef101d30701f86370f864d401f86570f86670f867d32f01f868f848f823bef2e06fd401f869d200018e99d30701aef84621b0f2d06bf847a4f867f84601b1f86601db3c9131e2d1f849f846f845c8f841cf16f84201cbfff84301cb07f84401ca00cccbfff84701cb07f84801cb2fccc9ed540905018ce001d30701f843baf2e069d401f900f845f900baf2e069d32f01f848baf2e069d401f900f849f900baf2e069d20001f2e069d3070101d1f845521078f40e6fa1f2e06a58db3c070136d3070101d1f845521078f40e6fa1f2e06a5230c705f2e06a59db3c07026e8f335ced44ed45ed478e983170c88210afaf283e580402cb1fcb3fcb1f80108050db3ced67ed65ed64727fed118aed41edf101f2ffdb030a0802b4f844f2d07002aef84621b0f2d06bf847a4f867f84601b1f86670c8821082609bf62402cb1fcb3f80108050db3cdb3cf849f846f845c8f841cf16f84201cbfff84301cb07f84401ca00cccbfff84701cb07f84801cb2fccc9ed540a090180f847f843ba8eb6f84170f849c8821075097f5d580502cb1fcb3ff84201cbfff84801cb2ff84701cb07f845f90001cbff13cc128010810090db3c7ff8649130e20a00888e40c85801cb055004cf1658fa02547120ed44ed45ed479d5bc85003cf17c9127158cb6acced67ed65ed64737fed11977001cb6a01cf17ed41edf101f2ffc901fb00db05000c5f03840ff2f000c1a1c771da89a1f48003f0c3a7fe03f0c441ae9380011c2660dbf0c6dbf0c8dbf0cadbf0ccdbf0cedbf0d11c45a60e03f0c7a40003f0c9a803f0cba7fe03f0cda60e03f0cfa65e03f0d1a803f0d3a3c5f083f085f087f089f08bf08df08ff091f09326d3be81" +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/errors.func b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/errors.func new file mode 100644 index 00000000..0e8f65fc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/errors.func @@ -0,0 +1,17 @@ +const int error::unauthorized_new_order = 1007; +const int error::invalid_new_order = 1008; +const int error::not_enough_ton = 100; +const int error::unauthorized_execute = 101; +const int error::singers_outdated = 102; +const int error::invalid_dictionary_sequence = 103; +const int error::unauthorized_init = 104; +const int error::already_inited = 105; +const int error::unauthorized_sign = 106; +const int error::already_approved = 107; +const int error::inconsistent_data = 108; +const int error::invalid_threshold = 109; +const int error::invalid_signers = 110; +const int error::expired = 111; +const int error::already_executed = 112; + +const int error::unknown_op = 0xffff; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/imports/stdlib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/imports/stdlib.fc new file mode 100644 index 00000000..c7bd3c3a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/imports/stdlib.fc @@ -0,0 +1,882 @@ +;; Standard library for funC +;; + +{- + This file is part of TON FunC Standard Library. + + FunC Standard Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + FunC Standard Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + +-} + +{- + # Tuple manipulation primitives + The names and the types are mostly self-explaining. + See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) + for more info on the polymorphic functions. + + Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) + and vise versa. +-} + +{- + # Lisp-style lists + + Lists can be represented as nested 2-elements tuples. + Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). + For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. +-} + +;;; Adds an element to the beginning of lisp-style list. +forall X -> tuple cons(X head, tuple tail) asm "CONS"; + +;;; Extracts the head and the tail of lisp-style list. +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; + +;;; Extracts the tail and the head of lisp-style list. +forall X -> (tuple, X) list_next(tuple list) asm(-> 1 0) "UNCONS"; + +;;; Returns the head of lisp-style list. +forall X -> X car(tuple list) asm "CAR"; + +;;; Returns the tail of lisp-style list. +tuple cdr(tuple list) asm "CDR"; + +;;; Creates tuple with zero elements. +tuple empty_tuple() asm "NIL"; + +;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` +;;; is of length at most 255. Otherwise throws a type check exception. +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; + +;;; Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) asm "SINGLE"; + +;;; Unpacks a tuple of length one +forall X -> X unsingle([X] t) asm "UNSINGLE"; + +;;; Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; + +;;; Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; + +;;; Creates a tuple of length three with given arguments as elements. +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; + +;;; Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; + +;;; Creates a tuple of length four with given arguments as elements. +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; + +;;; Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; + +;;; Returns the first element of a tuple (with unknown element types). +forall X -> X first(tuple t) asm "FIRST"; + +;;; Returns the second element of a tuple (with unknown element types). +forall X -> X second(tuple t) asm "SECOND"; + +;;; Returns the third element of a tuple (with unknown element types). +forall X -> X third(tuple t) asm "THIRD"; + +;;; Returns the fourth element of a tuple (with unknown element types). +forall X -> X fourth(tuple t) asm "3 INDEX"; + +;;; Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; + +;;; Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; + +;;; Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; + +;;; Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; + +;;; Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; + + +;;; Push null element (casted to given type) +;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. +;;; So `null` can actually have any atomic type. +forall X -> X null() asm "PUSHNULL"; + +;;; Moves a variable [x] to the top of the stack +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + + + +;;; Returns the current Unix time as an Integer +int now() asm "NOW"; + +;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. +slice my_address() asm "MYADDR"; + +;;; Returns the balance of the smart contract as a tuple consisting of an int +;;; (balance in nanotoncoins) and a `cell` +;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") +;;; at the start of Computation Phase. +;;; Note that RAW primitives such as [send_raw_message] do not update this field. +[int, cell] get_balance() asm "BALANCE"; + +;;; Returns the logical time of the current transaction. +int cur_lt() asm "LTIME"; + +;;; Returns the starting logical time of the current block. +int block_lt() asm "BLOCKLT"; + +;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. +int cell_hash(cell c) asm "HASHCU"; + +;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created +;;; and its hash computed by [cell_hash]. +int slice_hash(slice s) asm "HASHSU"; + +;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. +int string_hash(slice s) asm "SHA256U"; + +{- + # Signature checks +-} + +;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +;;; using [public_key] (also represented by a 256-bit unsigned integer). +;;; The signature must contain at least 512 data bits; only the first 512 bits are used. +;;; The result is `−1` if the signature is valid, `0` otherwise. +;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, +;;; the second hashing occurring inside `CHKSIGNS`. +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; + +;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, +;;; similarly to [check_signature]. +;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +;;; The verification of Ed25519 signatures is the standard one, +;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +{--- + # Computation of boc size + The primitives below may be useful for computing storage fees of user-provided data. +-} + +;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +;;; the identification of equal cells. +;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. +;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; +;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; + +;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. +;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; + +;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +{-- + # Debug primitives + Only works for local TVM execution with debug level verbosity +-} +;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. +() dump_stack() impure asm "DUMPSTK"; + +{- + # Persistent storage save and load +-} + +;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. +cell get_data() asm "c4 PUSH"; + +;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +() set_data(cell c) impure asm "c4 POP"; + +{- + # Continuation primitives +-} +;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. +;;; The primitive returns the current value of `c3`. +cont get_c3() impure asm "c3 PUSH"; + +;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +;;; Note that after execution of this primitive the current code +;;; (and the stack of recursive function calls) won't change, +;;; but any other function call will use a function from the new code. +() set_c3(cont c) impure asm "c3 POP"; + +;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. +cont bless(slice s) impure asm "BLESS"; + +{--- + # Gas related primitives +-} + +;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +;;; decreasing the value of `gr` by `gc` in the process. +;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. +;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. +;;; +;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). +() accept_message() impure asm "ACCEPT"; + +;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +;;; an (unhandled) out of gas exception is thrown before setting new gas limits. +;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; + +;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +;;; so that the current execution is considered “successful” with the saved values even if an exception +;;; in Computation Phase is thrown later. +() commit() impure asm "COMMIT"; + +;;; Not implemented +;;; Computes the amount of gas that can be bought for `amount` nanoTONs, +;;; and sets `gl` accordingly in the same way as [set_gas_limit]. +;;() buy_gas(int amount) impure asm "BUYGAS"; + +;;; Computes the minimum of two integers [x] and [y]. +int min(int x, int y) asm "MIN"; + +;;; Computes the maximum of two integers [x] and [y]. +int max(int x, int y) asm "MAX"; + +;;; Sorts two integers. +(int, int) minmax(int x, int y) asm "MINMAX"; + +;;; Computes the absolute value of an integer [x]. +int abs(int x) asm "ABS"; + +{- + # Slice primitives + + It is said that a primitive _loads_ some data, + if it returns the data and the remainder of the slice + (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). + + It is said that a primitive _preloads_ some data, if it returns only the data + (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). + + Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. +-} + + +;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. +slice begin_parse(cell c) asm "CTOS"; + +;;; Checks if [s] is empty. If not, throws an exception. +() end_parse(slice s) impure asm "ENDS"; + +;;; Loads the first reference from the slice. +(slice, cell) load_ref(slice s) asm(-> 1 0) "LDREF"; + +;;; Preloads the first reference from the slice. +cell preload_ref(slice s) asm "PLDREF"; + +{- Functions below are commented because are implemented on compilator level for optimisation -} + +;;; Loads a signed [len]-bit integer from a slice [s]. +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; + +;;; Loads an unsigned [len]-bit integer from a slice [s]. +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; + +;;; Preloads a signed [len]-bit integer from a slice [s]. +;; int preload_int(slice s, int len) asm "PLDIX"; + +;;; Preloads an unsigned [len]-bit integer from a slice [s]. +;; int preload_uint(slice s, int len) asm "PLDUX"; + +;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; + +;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; + +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). +(slice, int) load_grams(slice s) asm(-> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm(-> 1 0) "LDVARUINT16"; + +;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; + +;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice first_bits(slice s, int len) asm "SDCUTFIRST"; + +;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; + +;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. +;;; (returns `null` if `nothing` constructor is used). +(slice, cell) load_dict(slice s) asm(-> 1 0) "LDDICT"; + +;;; Preloads a dictionary `D` from `slice` [s]. +cell preload_dict(slice s) asm "PLDDICT"; + +;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. +slice skip_dict(slice s) asm "SKIPDICT"; +(slice, ()) ~skip_dict(slice s) asm "SKIPDICT"; + +;;; Loads (Maybe ^Cell) from `slice` [s]. +;;; In other words loads 1 bit and if it is true +;;; loads first ref and return it with slice remainder +;;; otherwise returns `null` and slice remainder +(slice, cell) load_maybe_ref(slice s) asm(-> 1 0) "LDOPTREF"; + +;;; Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; + + +;;; Returns the depth of `cell` [c]. +;;; If [c] has no references, then return `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +;;; If [c] is a `null` instead of a cell, returns zero. +int cell_depth(cell c) asm "CDEPTH"; + + +{- + # Slice size primitives +-} + +;;; Returns the number of references in `slice` [s]. +int slice_refs(slice s) asm "SREFS"; + +;;; Returns the number of data bits in `slice` [s]. +int slice_bits(slice s) asm "SBITS"; + +;;; Returns both the number of data bits and the number of references in `slice` [s]. +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; + +;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). +int slice_empty?(slice s) asm "SEMPTY"; + +;;; Checks whether `slice` [s] has no bits of data. +int slice_data_empty?(slice s) asm "SDEMPTY"; + +;;; Checks whether `slice` [s] has no references. +int slice_refs_empty?(slice s) asm "SREMPTY"; + +;;; Returns the depth of `slice` [s]. +;;; If [s] has no references, then returns `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +int slice_depth(slice s) asm "SDEPTH"; + +{- + # Builder size primitives +-} + +;;; Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) asm "BREFS"; + +;;; Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) asm "BBITS"; + +;;; Returns the depth of `builder` [b]. +;;; If no cell references are stored in [b], then returns 0; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +int builder_depth(builder b) asm "BDEPTH"; + +{- + # Builder primitives + It is said that a primitive _stores_ a value `x` into a builder `b` + if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. + It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). + + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. +-} + +;;; Creates a new empty `builder`. +builder begin_cell() asm "NEWC"; + +;;; Converts a `builder` into an ordinary `cell`. +cell end_cell(builder b) asm "ENDC"; + +;;; Stores a reference to `cell` [c] into `builder` [b]. +builder store_ref(builder b, cell c) asm(c b) "STREF"; + +;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; + +;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; + + +;;; Stores `slice` [s] into `builder` [b] +builder store_slice(builder b, slice s) asm "STSLICER"; + +;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b]. +;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, +;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +;;; followed by an `8l`-bit unsigned big-endian representation of [x]. +;;; If [x] does not belong to the supported range, a range check exception is thrown. +;;; +;;; Store amounts of TonCoins to the builder as VarUInteger 16 +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) asm "STVARUINT16"; + +;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. +;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +;;; Stores (Maybe ^Cell) to builder: +;;; if cell is null store 1 zero bit +;;; otherwise store 1 true bit and ref to cell +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + + +{- + # Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. +-} + +;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, +;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. +(slice, slice) load_msg_addr(slice s) asm(-> 1 0) "LDMSGADDR"; + +;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. +tuple parse_addr(slice s) asm "PARSEMSGADDR"; + +;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, +;;; and returns both the workchain and the 256-bit address as integers. +;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +;;; throws a cell deserialization exception. +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; + +;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], +;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +{- + # Dictionary primitives +-} + + +;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; + +;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; + +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; + +;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL +cell new_dict() asm "NEWDICT"; +;;; Checks whether a dictionary is empty. Equivalent to cell_null?. +int dict_empty?(cell c) asm "DICTEMPTY"; + + +{- Prefix dictionary primitives -} +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. +cell config_param(int x) asm "CONFIGOPTPARAM"; +;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. +int cell_null?(cell c) asm "ISNULL"; + +;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract +() set_code(cell new_code) impure asm "SETCODE"; + +;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. +int random() impure asm "RANDU256"; +;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. +int rand(int range) impure asm "RAND"; +;;; Returns the current random seed as an unsigned 256-bit Integer. +int get_seed() impure asm "RANDSEED"; +;;; Sets the random seed to unsigned 256-bit seed. +() set_seed(int x) impure asm "SETRAND"; +;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. +() randomize(int x) impure asm "ADDRAND"; +;;; Equivalent to randomize(cur_lt());. +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +;;; Checks whether the data parts of two slices coinside +int equal_slices_bits(slice a, slice b) asm "SDEQ"; +;;; Checks whether b is a null. Note, that FunC also has polymorphic null? built-in. +int builder_null?(builder b) asm "ISNULL"; +;;; Concatenates two builders +builder store_builder(builder to, builder from) asm "STBR"; + +;; CUSTOM: + +;; TVM UPGRADE 2023-07 https://docs.ton.org/learn/tvm-instructions/tvm-upgrade-2023-07 +;; In mainnet since 20 Dec 2023 https://t.me/tonblockchain/226 + +;;; Retrieves code of smart-contract from c7 +cell my_code() asm "MYCODE"; + +;;; Creates an output action and returns a fee for creating a message. Mode has the same effect as in the case of SENDRAWMSG +int send_message(cell msg, int mode) impure asm "SENDMSG"; + +int gas_consumed() asm "GASCONSUMED"; + +;; TVM V6 https://github.com/ton-blockchain/ton/blob/testnet/doc/GlobalVersions.md#version-6 + +int get_compute_fee(int workchain, int gas_used) asm(gas_used workchain) "GETGASFEE"; +int get_storage_fee(int workchain, int seconds, int bits, int cells) asm(cells bits seconds workchain) "GETSTORAGEFEE"; +int get_forward_fee(int workchain, int bits, int cells) asm(cells bits workchain) "GETFORWARDFEE"; +int get_precompiled_gas_consumption() asm "GETPRECOMPILEDGAS"; + +int get_simple_compute_fee(int workchain, int gas_used) asm(gas_used workchain) "GETGASFEESIMPLE"; +int get_simple_forward_fee(int workchain, int bits, int cells) asm(cells bits workchain) "GETFORWARDFEESIMPLE"; +int get_original_fwd_fee(int workchain, int fwd_fee) asm(fwd_fee workchain) "GETORIGINALFWDFEE"; +int my_storage_due() asm "DUEPAYMENT"; + +tuple get_fee_cofigs() asm "UNPACKEDCONFIGTUPLE"; + +;; BASIC + +const int TRUE = -1; +const int FALSE = 0; + +const int MASTERCHAIN = -1; +const int BASECHAIN = 0; + +const int CELL_BITS = 1023; +const int CELL_REFS = 4; + +;;; skip (Maybe ^Cell) from `slice` [s]. +(slice, ()) ~skip_maybe_ref(slice s) asm "SKIPOPTREF"; + +(slice, int) ~load_bool(slice s) inline { + return s.load_int(1); +} + +builder store_bool(builder b, int value) inline { + return b.store_int(value, 1); +} + +;; ADDRESS NONE +;; addr_none$00 = MsgAddressExt; https://github.com/ton-blockchain/ton/blob/8a9ff339927b22b72819c5125428b70c406da631/crypto/block/block.tlb#L100 + +builder store_address_none(builder b) inline { + return b.store_uint(0, 2); +} + +slice address_none() asm " = 1); + throw_unless(error::invalid_threshold, threshold > 0); + throw_unless(error::invalid_threshold, threshold <= signers_num); + + proposers = action~load_dict(); + validate_dictionary_sequence(proposers); + + action.end_parse(); + } + } + } until (~ found?); + + return ((threshold, signers, signers_num, proposers), ()); +} + + +(int, int, cell, int, cell, int) load_data() inline { + slice ds = get_data().begin_parse(); + var data = ( + ds~load_order_seqno(), ;; next_order_seqno + ds~load_index(), ;; threshold + ds~load_nonempty_dict(), ;; signers + ds~load_index(), ;; signers_num + ds~load_dict(), ;; proposers + ds~load_bool() ;; allow_arbitrary_order_seqno + ); + ds.end_parse(); + return data; +} + +() save_data(int next_order_seqno, int threshold, cell signers, int signers_num, cell proposers, int allow_arbitrary_order_seqno) impure inline { + set_data( + begin_cell() + .store_order_seqno(next_order_seqno) + .store_index(threshold) + .store_nonempty_dict(signers) + .store_index(signers_num) + .store_dict(proposers) + .store_bool(allow_arbitrary_order_seqno) + .end_cell() + ); +} + +() recv_internal(int balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + slice in_msg_full_slice = in_msg_full.begin_parse(); + int msg_flags = in_msg_full_slice~load_msg_flags(); + if (msg_flags & 1) { ;; is bounced + return (); + } + slice sender_address = in_msg_full_slice~load_msg_addr(); + + if (in_msg_body.slice_bits() == 0) { + return (); ;; empty message - just accept TONs + } + + int op = in_msg_body~load_op(); + + if (op == 0) { + return (); ;; simple text message - just accept TONs + } + + int query_id = in_msg_body~load_query_id(); + + (int next_order_seqno, int threshold, cell signers, int signers_num, cell proposers, int allow_arbitrary_order_seqno) = load_data(); + + if (op == op::new_order) { + int order_seqno = in_msg_body~load_order_seqno(); + if (~ allow_arbitrary_order_seqno) { + if (order_seqno == MAX_ORDER_SEQNO) { + order_seqno = next_order_seqno; + } else { + throw_unless(error::invalid_new_order, (order_seqno == next_order_seqno)); + } + next_order_seqno += 1; + } + + int signer? = in_msg_body~load_bool(); + int index = in_msg_body~load_index(); + int expiration_date = in_msg_body~load_timestamp(); + cell order_body = in_msg_body~load_ref(); + in_msg_body.end_parse(); + (slice expected_address, int found?) = (signer? ? signers : proposers).udict_get?(INDEX_SIZE, index); + throw_unless(error::unauthorized_new_order, found?); + throw_unless(error::unauthorized_new_order, equal_slices_bits(sender_address, expected_address)); + throw_unless(error::expired, expiration_date >= now()); + + int minimal_value = calculate_order_processing_cost(order_body, signers, expiration_date - now()); + throw_unless(error::not_enough_ton, msg_value >= minimal_value); + + cell state_init = calculate_order_state_init(my_address(), order_seqno); + slice order_address = calculate_address_by_state_init(BASECHAIN, state_init); + builder init_body = begin_cell() + .store_op_and_query_id(op::init, query_id) + .store_index(threshold) + .store_nonempty_dict(signers) + .store_timestamp(expiration_date) + .store_ref(order_body) + .store_bool(signer?); + + if (signer?) { + init_body = init_body.store_index(index); + } + send_message_with_state_init_and_body( + order_address, + 0, + state_init, + init_body, + BOUNCEABLE, + SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE | SEND_MODE_BOUNCE_ON_ACTION_FAIL + ); + + } elseif (op == op::execute) { + ;; check that sender is order smart-contract and check that it has recent + ;; signers dict + + int order_seqno = in_msg_body~load_order_seqno(); + int expiration_date = in_msg_body~load_timestamp(); + int approvals_num = in_msg_body~load_index(); + int signers_hash = in_msg_body~load_hash(); + cell order_body = in_msg_body~load_ref(); + in_msg_body.end_parse(); + + cell state_init = calculate_order_state_init(my_address(), order_seqno); + slice order_address = calculate_address_by_state_init(BASECHAIN, state_init); + + throw_unless(error::unauthorized_execute, equal_slices_bits(sender_address, order_address)); + throw_unless(error::singers_outdated, (signers_hash == signers.cell_hash()) & (approvals_num >= threshold)); + throw_unless(error::expired, expiration_date >= now()); + + (threshold, signers, signers_num, proposers)~execute_order(order_body); + } elseif (op == op::execute_internal) { + ;; we always trust ourselves, this feature is used to make chains of executions + ;; where last action of previous execution triggers new one. + + throw_unless(error::unauthorized_execute, equal_slices_bits(sender_address, my_address())); + cell order_body = in_msg_body~load_ref(); + in_msg_body.end_parse(); + (threshold, signers, signers_num, proposers)~execute_order(order_body); + } + + save_data(next_order_seqno, threshold, signers, signers_num, proposers, allow_arbitrary_order_seqno); +} + +(int, int, cell, cell) get_multisig_data() method_id { + (int next_order_seqno, int threshold, cell signers, int signers_num, cell proposers, int allow_arbitrary_order_seqno) = load_data(); + throw_unless(error::inconsistent_data, signers_num == validate_dictionary_sequence(signers)); + validate_dictionary_sequence(proposers); + throw_unless(error::invalid_signers, signers_num >= 1); + throw_unless(error::invalid_threshold, threshold > 0); + throw_unless(error::invalid_threshold, threshold <= signers_num); + return (allow_arbitrary_order_seqno ? -1 : next_order_seqno, threshold, signers, proposers); +} + +int get_order_estimate(cell order, int expiration_date) method_id { + (_, _, cell signers, _, _, _) = load_data(); + return calculate_order_processing_cost(order, signers, expiration_date - now()); +} + +slice get_order_address(int order_seqno) method_id { + cell state_init = calculate_order_state_init(my_address(), order_seqno); + return calculate_address_by_state_init(BASECHAIN, state_init); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/op-codes.func b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/op-codes.func new file mode 100644 index 00000000..6db22585 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/op-codes.func @@ -0,0 +1,12 @@ +const int op::new_order = 0xf718510f; +const int op::execute = 0x75097f5d; +const int op::execute_internal = 0xa32c59bf; + +const int op::init = 0x9c73fba2; +const int op::approve = 0xa762230f; +const int op::approve_accepted = 0x82609bf6; +const int op::approve_rejected = 0xafaf283e; + +const int actions::send_message = 0xf1381e5b; +const int actions::update_multisig_params = 0x1d0cfbd3; + diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/order.func b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/order.func new file mode 100644 index 00000000..12c69b82 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/order.func @@ -0,0 +1,258 @@ +#include "imports/stdlib.fc"; +#include "types.func"; +#include "op-codes.func"; +#include "messages.func"; +#include "errors.func"; + +;; DATA + +global slice multisig_address; +global int order_seqno; +global int threshold; +global int sent_for_execution?; +global cell signers; +global int approvals_mask; +global int approvals_num; +global int expiration_date; +global cell order; + +() load_data() impure inline { + slice ds = get_data().begin_parse(); + multisig_address = ds~load_msg_addr(); + order_seqno = ds~load_order_seqno(); + + if (ds.slice_bits() == 0) { + ;; not initialized yet + threshold = null(); + sent_for_execution? = null(); + signers = null(); + approvals_mask = null(); + approvals_num = null(); + expiration_date = null(); + + } else { + threshold = ds~load_index(); + sent_for_execution? = ds~load_bool(); + signers = ds~load_nonempty_dict(); + approvals_mask = ds~load_uint(MASK_SIZE); + approvals_num = ds~load_index(); + expiration_date = ds~load_timestamp(); + order = ds~load_ref(); + ds.end_parse(); + } +} + +() save_data() impure inline { + set_data( + begin_cell() + .store_slice(multisig_address) + .store_order_seqno(order_seqno) + .store_index(threshold) + .store_bool(sent_for_execution?) + .store_nonempty_dict(signers) + .store_uint(approvals_mask, MASK_SIZE) + .store_index(approvals_num) + .store_timestamp(expiration_date) + .store_ref(order) + .end_cell() + ); +} + +;; UTILS + +slice get_text_comment(slice in_msg_body) impure inline { + if (in_msg_body.slice_refs() == 0) { + return in_msg_body; + } + + ;;combine comment into one slice + builder combined_string = begin_cell(); + int need_exit = false; + do { + ;; store all bits from current cell + ;; it's ok to overflow here, it means that comment is incorrect + combined_string = combined_string.store_slice(in_msg_body.preload_bits(in_msg_body.slice_bits())); + ;;and go to the next + + if (in_msg_body.slice_refs()) { + in_msg_body = in_msg_body.preload_ref().begin_parse(); + } else { + need_exit = true; + } + + } until (need_exit); + return combined_string.end_cell().begin_parse(); +} + +(int, int) find_signer_by_address(slice signer_address) impure inline { + int found_signer? = false; + int signer_index = -1; + do { + (signer_index, slice value, int next_found?) = signers.udict_get_next?(INDEX_SIZE, signer_index); + if (next_found?) { + if (equal_slices_bits(signer_address, value)) { + found_signer? = true; + next_found? = false; ;; fast way to exit loop + } + } + } until (~ next_found?); + return (signer_index, found_signer?); +} + +() add_approval(int signer_index) impure inline { + int mask = 1 << signer_index; + throw_if(error::already_approved, approvals_mask & mask); + approvals_num += 1; + approvals_mask |= mask; +} + +() try_execute(int query_id) impure inline_ref { + if (approvals_num == threshold) { + send_message_with_only_body( + multisig_address, + 0, + begin_cell() + .store_op_and_query_id(op::execute, query_id) + .store_order_seqno(order_seqno) + .store_timestamp(expiration_date) + .store_index(approvals_num) + .store_hash(signers.cell_hash()) + .store_ref(order), + NON_BOUNCEABLE, + SEND_MODE_CARRY_ALL_BALANCE | SEND_MODE_BOUNCE_ON_ACTION_FAIL + ); + sent_for_execution? = true; + } +} + +() approve(int signer_index, slice response_address, int query_id) impure inline_ref { + try { + throw_if(error::already_executed, sent_for_execution?); + + add_approval(signer_index); + + send_message_with_only_body( + response_address, + 0, + begin_cell().store_op_and_query_id(op::approve_accepted, query_id), + NON_BOUNCEABLE, + SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE | SEND_MODE_BOUNCE_ON_ACTION_FAIL + ); + + try_execute(query_id); + + save_data(); + + } catch (_, exit_code) { + send_message_with_only_body( + response_address, + 0, + begin_cell() + .store_op_and_query_id(op::approve_rejected, query_id) + .store_uint(exit_code, 32), + NON_BOUNCEABLE, + SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE | SEND_MODE_BOUNCE_ON_ACTION_FAIL + ); + } +} + +;; RECEIVE + +() recv_internal(int balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + slice in_msg_full_slice = in_msg_full.begin_parse(); + int msg_flags = in_msg_full_slice~load_msg_flags(); + if (msg_flags & 1) { ;; is bounced + return (); + } + slice sender_address = in_msg_full_slice~load_msg_addr(); + + int op = in_msg_body~load_op(); + + load_data(); + + if (op == 0) { + ;; message with text comment + slice text_comment = get_text_comment(in_msg_body); + throw_unless(error::unknown_op, equal_slices_bits(text_comment, "approve")); + + (int signer_index, int found_signer?) = find_signer_by_address(sender_address); + throw_unless(error::unauthorized_sign, found_signer?); + + approve(signer_index, sender_address, cur_lt()); + return (); + } + + int query_id = in_msg_body~load_query_id(); + + if (op == op::init) { + throw_unless(error::unauthorized_init, equal_slices_bits(sender_address, multisig_address)); + + if (null?(threshold)) { + ;; Let's init + threshold = in_msg_body~load_index(); + sent_for_execution? = false; + signers = in_msg_body~load_nonempty_dict(); + approvals_mask = 0; + approvals_num = 0; + expiration_date = in_msg_body~load_timestamp(); + throw_unless(error::expired, expiration_date >= now()); ;; in case of error TONs will bounce back to multisig + order = in_msg_body~load_ref(); + + int approve_on_init? = in_msg_body~load_bool(); + if (approve_on_init?) { + int signer_index = in_msg_body~load_index(); + add_approval(signer_index); + try_execute(query_id); + } + in_msg_body.end_parse(); + save_data(); + return (); + } else { + ;; order is inited second time, if it is inited by another oracle + ;; we count it as approval + throw_unless(error::already_inited, in_msg_body~load_index() == threshold); + throw_unless(error::already_inited, in_msg_body~load_nonempty_dict().cell_hash() == signers.cell_hash()); + throw_unless(error::already_inited,in_msg_body~load_timestamp() == expiration_date); + throw_unless(error::already_inited, in_msg_body~load_ref().cell_hash() == order.cell_hash()); + + int approve_on_init? = in_msg_body~load_bool(); + throw_unless(error::already_inited, approve_on_init?); + int signer_index = in_msg_body~load_index(); + in_msg_body.end_parse(); + (slice signer_address, int found?) = signers.udict_get?(INDEX_SIZE, signer_index); + throw_unless(error::unauthorized_sign, found?); + approve(signer_index, signer_address, query_id); + return (); + } + } + + if (op == op::approve) { + int signer_index = in_msg_body~load_index(); + in_msg_body.end_parse(); + (slice signer_address, int found?) = signers.udict_get?(INDEX_SIZE, signer_index); + throw_unless(error::unauthorized_sign, found?); + throw_unless(error::unauthorized_sign, equal_slices_bits(sender_address, signer_address)); + + approve(signer_index, sender_address, query_id); + return (); + } + + throw(error::unknown_op); +} + +;; GET-METHODS + +_ get_order_data() method_id { + load_data(); + return ( + multisig_address, + order_seqno, + threshold, + sent_for_execution?, + signers, + approvals_mask, + approvals_num, + expiration_date, + order + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/order_helpers.func b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/order_helpers.func new file mode 100644 index 00000000..91952be1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/order_helpers.func @@ -0,0 +1,89 @@ +#include "imports/stdlib.fc"; +#include "types.func"; +#include "auto/order_code.func"; + +cell pack_order_init_data(slice multisig_address, int order_seqno) inline { + return begin_cell() + .store_slice(multisig_address) + .store_order_seqno(order_seqno) + .end_cell(); +} + +cell calculate_order_state_init(slice multisig_address, int order_seqno) inline { + {- + https://github.com/ton-blockchain/ton/blob/8a9ff339927b22b72819c5125428b70c406da631/crypto/block/block.tlb#L144 + _ split_depth:(Maybe (## 5)) special:(Maybe TickTock) + code:(Maybe ^Cell) data:(Maybe ^Cell) + library:(Maybe ^Cell) = StateInit; + -} + return begin_cell() + .store_uint(0, 2) ;; 0b00 - No split_depth; No special + .store_maybe_ref(order_code()) + .store_maybe_ref(pack_order_init_data(multisig_address, order_seqno)) + .store_uint(0, 1) ;; Empty libraries + .end_cell(); +} + +slice calculate_address_by_state_init(int workchain, cell state_init) inline { + {- + https://github.com/ton-blockchain/ton/blob/8a9ff339927b22b72819c5125428b70c406da631/crypto/block/block.tlb#L105 + addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; + -} + return begin_cell() + .store_uint(4, 3) ;; 0b100 = addr_std$10 tag; No anycast + .store_int(workchain, 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +;;; @see /description.md "How is it calculated" + +const int MULTISIG_INIT_ORDER_GAS = 10232; ;; 255 signers 61634 gas total - size_counting gas(51402) +const int ORDER_INIT_GAS = 4614; +const int ORDER_EXECUTE_GAS = 11244; ;; 255 signers increases lookup costs +const int MULTISIG_EXECUTE_GAS = 7576; ;; For single transfer action order +;; we call number of bits/cells without order bits/cells as "overhead" +const int INIT_ORDER_BIT_OVERHEAD = 1337; +const int INIT_ORDER_CELL_OVERHEAD = 6; +const int ORDER_STATE_BIT_OVERHEAD = 1760; +const int ORDER_STATE_CELL_OVERHEAD = 6; +const int EXECUTE_ORDER_BIT_OVERHEAD = 664; +const int EXECUTE_ORDER_CELL_OVERHEAD = 1; + +int calculate_order_processing_cost(cell order_body, cell signers, int duration) inline { + {- There are following costs: + 1) Gas cost on Multisig contract + 2) Forward cost for Multisig->Order message + 3) Gas cost on Order initialisation + 4) Storage cost on Order + 5) Gas cost on Order finalization + 6) Forward cost for Order->Multisig message + 7) Gas cost on Multisig till accept_message + -} + + ;; compute_data_size is unpredictable in gas, so we need to measure gas prior to it and after + ;; and add difference to MULTISIG_INIT_ORDER_GAS + int initial_gas = gas_consumed(); + (int order_cells, int order_bits, _) = compute_data_size(order_body, 8192); ;; max cells in external message = 8192 + (int signers_cells, int signers_bits, _) = compute_data_size(signers, 512); ;; max 255 signers in dict, this max cells in dict = 511 + int size_counting_gas = gas_consumed() - initial_gas; + + int gas_fees = get_compute_fee(BASECHAIN,MULTISIG_INIT_ORDER_GAS + size_counting_gas) + + get_compute_fee(BASECHAIN, ORDER_INIT_GAS) + + get_compute_fee(BASECHAIN, ORDER_EXECUTE_GAS) + + get_compute_fee(BASECHAIN, MULTISIG_EXECUTE_GAS); + + int forward_fees = get_forward_fee(BASECHAIN, + INIT_ORDER_BIT_OVERHEAD + order_bits + signers_bits, + INIT_ORDER_CELL_OVERHEAD + order_cells + signers_cells) + + get_forward_fee(BASECHAIN, + EXECUTE_ORDER_BIT_OVERHEAD + order_bits, + EXECUTE_ORDER_CELL_OVERHEAD + order_cells); + + + int storage_fees = get_storage_fee(BASECHAIN, duration, + ORDER_STATE_BIT_OVERHEAD + order_bits + signers_bits, + ORDER_STATE_CELL_OVERHEAD + order_cells + signers_cells); + return gas_fees + forward_fees + storage_fees; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/types.func b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/types.func new file mode 100644 index 00000000..2664a5fa --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/multisig/types.func @@ -0,0 +1,52 @@ +;; Multisig types + +#include "imports/stdlib.fc"; + +;; Alias for load_ref +(slice, cell) load_nonempty_dict(slice s) asm(-> 1 0) "LDREF"; + +;; alias for store_ref +builder store_nonempty_dict(builder b, cell c) asm(c b) "STREF"; + +const int TIMESTAMP_SIZE = 48; + +(slice, int) ~load_timestamp(slice s) inline { + return s.load_uint(TIMESTAMP_SIZE); +} +builder store_timestamp(builder b, int timestamp) inline { + return b.store_uint(timestamp, TIMESTAMP_SIZE); +} + +const int HASH_SIZE = 256; + +(slice, int) ~load_hash(slice s) inline { + return s.load_uint(HASH_SIZE); +} +builder store_hash(builder b, int hash) inline { + return b.store_uint(hash, HASH_SIZE); +} + +{- By index we mean index of signer in signers dictionary. The same type is used + for threshold, singers number and for proposers indexes -} +const int INDEX_SIZE = 8; +const int MASK_SIZE = 1 << INDEX_SIZE; + +(slice, int) ~load_index(slice s) inline { + return s.load_uint(INDEX_SIZE); +} +builder store_index(builder b, int index) inline { + return b.store_uint(index, INDEX_SIZE); +} + +const int ACTION_INDEX_SIZE = 8; + + +const int ORDER_SEQNO_SIZE = 256; +const int MAX_ORDER_SEQNO = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + +(slice, int) ~load_order_seqno(slice s) inline { + return s.load_uint(ORDER_SEQNO_SIZE); +} +builder store_order_seqno(builder b, int seqno) inline { + return b.store_uint(seqno, ORDER_SEQNO_SIZE); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/callbackOpcodes.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/callbackOpcodes.fc new file mode 100644 index 00000000..e53c3060 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/callbackOpcodes.fc @@ -0,0 +1,10 @@ +;; Send flow +const int Layerzero::OP::CHANNEL_SEND_CALLBACK = "Layerzero::OP::CHANNEL_SEND_CALLBACK"c; + +;; Receive flow +const int Layerzero::OP::LZ_RECEIVE_PREPARE = "Layerzero::OP::LZ_RECEIVE_PREPARE"c; +const int Layerzero::OP::LZ_RECEIVE_EXECUTE = "Layerzero::OP::LZ_RECEIVE_EXECUTE"c; + +;; Receive flow management +const int Layerzero::OP::BURN_CALLBACK = "Layerzero::OP::BURN_CALLBACK"c; +const int Layerzero::OP::NILIFY_CALLBACK = "Layerzero::OP::NILIFY_CALLBACK"c; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/handler.fc new file mode 100644 index 00000000..a4efc280 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/handler.fc @@ -0,0 +1,1108 @@ +#include "callbackOpcodes.fc"; + +#include "../core/abstract/protocolHandler.fc"; + +#include "../../funC++/actions/dispatch.fc"; +#include "../../funC++/actions/event.fc"; + +#include "../../classes/lz/EpConfig.fc"; +#include "../../classes/lz/Packet.fc"; +#include "../../classes/lz/Path.fc"; +#include "../../classes/lz/ReceiveEpConfig.fc"; +#include "../../classes/lz/SendEpConfig.fc"; + +#include "../../classes/msgdata/ChannelNonceInfo.fc"; +#include "../../classes/msgdata/CoinsAmount.fc"; +#include "../../classes/msgdata/ExtendedMd.fc"; +#include "../../classes/msgdata/LzReceivePrepare.fc"; +#include "../../classes/msgdata/LzReceiveStatus.fc"; +#include "../../classes/msgdata/LzReceivePrepare.fc"; +#include "../../classes/msgdata/LzSend.fc"; +#include "../../classes/msgdata/MdAddress.fc"; +#include "../../classes/msgdata/MdObj.fc"; +#include "../../classes/msgdata/MessagingReceipt.fc"; +#include "../../classes/msgdata/MsglibSendCallback.fc"; +#include "../../classes/msgdata/Nonce.fc"; +#include "../../classes/msgdata/PacketId.fc"; +#include "../../classes/msgdata/PacketSent.fc"; + + +#include "../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../funC++/dataStructures/PipelinedOutOfOrder.fc"; + +#include "../interfaces.fc"; +#include "../msglibs/interface.fc"; +#include "interface.fc"; +#include "storage.fc"; + +;;; ================INTERFACE FUNCTIONS===================== + +int _getEventSink() inline { + return getOwner(); +} + +;;; ==========================HELPER FUNCTIONS===================================== + +() _assertEqualPaths(cell $path1, cell $path2) impure inline { + throw_unless(Channel::ERROR::wrongPath, $path1.cl::hash() == $path2.cl::hash()); +} + +;; @info The send request queue (Channel::sendRequestQueue) is a DeterministicInsertionCircularQueue +;; that stores a mapping from requestId => hash of LzSend object. +;; {_build, _read}SendRequestQueueEntry functions are helper functions that +;; serialize and deserialize the 256-bit hash that is stored in the DICQueue +cell _buildSendRequestQueueEntry(cell $lzSend) impure inline method_id { + return begin_cell().store_uint256($lzSend.cl::hash()).end_cell(); +} + +int _readSendRequestQueueEntry(cell contents) impure inline method_id { + if (contents.is_null()) { + return 0; + } elseif (contents.cell_is_empty()) { + return 0; + } + return contents.begin_parse().preload_uint(256); +} + +;; returns boolean committable, (packet or null) +(int, cell) _nonceCommittable(int incomingNonce) impure inline method_id { + throw_if(Channel::ERROR::invalidNonce, incomingNonce <= 0); + + cell $storage = getContractStorage(); + + cell $executePOOO = $storage.Channel::getExecutePOOO(); + int firstUnexecutedNonce = $executePOOO.POOO::getNextEmpty(); + + (int actualKey, cell $packet, int status, int exists) = DeterministicInsertionCircularQueue::get( + $storage.Channel::getExecutionQueue(), + incomingNonce + ); + + if ( + (incomingNonce == firstUnexecutedNonce) + & (actualKey == incomingNonce) + & (status != ExecutionQueue::executing) + ) { + ;; short-circuit for efficiency in the common case + return (true, exists ? $packet : null()); + } + + ;; condition 1 & 2: must be within the window + ;; condition 3: must not be executing + if ( + (incomingNonce >= firstUnexecutedNonce) + & (incomingNonce <= POOO::maxSettableBit($executePOOO)) + & (status != ExecutionQueue::executing) + ) { + ;; this is nested because funC doesn't support short-circuiting boolean/bitwise ops + ;; condition 4: must not be executed + ifnot (POOO::isBitSet($executePOOO, incomingNonce)) { + return (true, exists ? $packet : null()); + } + } + + return (false, null()); +} + +;; returns boolean committable +int _optimizedNonceCommittable(cell $executePOOO, cell executionQueue, int incomingNonce) impure inline { + throw_if(Channel::ERROR::invalidNonce, incomingNonce <= 0); + + int firstUnexecutedNonce = $executePOOO.POOO::getNextEmpty(); + + (int actualKey, _, int status, int exists) = DeterministicInsertionCircularQueue::get( + executionQueue, + incomingNonce + ); + + ;; condition 1 & 2: must be within the window + ;; condition 3: must not be executing + if ( + (incomingNonce >= firstUnexecutedNonce) + & (incomingNonce <= POOO::maxSettableBit($executePOOO)) + & (status != ExecutionQueue::executing) + ) { + ;; this is nested because funC doesn't support short-circuiting boolean/bitwise ops + ;; condition 4: must not be executed + ifnot (POOO::isBitSet($executePOOO, incomingNonce)) { + return true; + } + } + + return false; +} + +cell _getExecutablePacket(int incomingNonce) impure inline method_id { + (int isCommittable, cell $packet) = _nonceCommittable(incomingNonce); + int firstUncommittedNonce = getContractStorage() + .Channel::getCommitPOOO() + .POOO::getNextEmpty(); + + throw_if( + Channel::ERROR::notExecutable, + ((~ isCommittable) | (incomingNonce >= firstUncommittedNonce) | $packet.is_null()) + ); + + return $packet; +} + +;;; ==========================VIEW FUNCTIONS===================================== + +int _viewInboundNonce() impure method_id { + return getContractStorage() + .Channel::getCommitPOOO() + .POOO::getNextEmpty() - 1; +} + +int _viewExecutionStatus(int incomingNonce) impure method_id { + cell $storage = getContractStorage(); + + cell $executePOOO = $storage.cl::get(Channel::executePOOO); + int firstUnexecutedNonce = $executePOOO.cl::get(POOO::nextEmpty); + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + int firstUncommittedNonce = $commitPOOO.cl::get(POOO::nextEmpty); + int inboundNonce = firstUncommittedNonce - 1; + + int executed = incomingNonce < firstUnexecutedNonce; + if ((~ executed) & (incomingNonce < (firstUnexecutedNonce + MAX_CELL_BITS))) { + executed = $executePOOO.POOO::isBitSet(incomingNonce); + } + + int committed = incomingNonce < firstUncommittedNonce; + if ((~ committed) & (incomingNonce < (firstUncommittedNonce + MAX_CELL_BITS))) { + committed = $commitPOOO.POOO::isBitSet(incomingNonce); + } + + ifnot (committed) { + return ExecutionStatus::uncommitted; + } elseif (executed) { + return ExecutionStatus::executed; + } + + (_, _, int status, _) = DeterministicInsertionCircularQueue::get( + $storage.cl::get(Channel::executionQueue), + incomingNonce + ); + if (status == ExecutionQueue::executing) { + return ExecutionStatus::executing; + } elseif (incomingNonce <= inboundNonce) { + return ExecutionStatus::executable; + } + return ExecutionStatus::committedNotExecutable; +} + +;;; ================INTERFACE FUNCTIONS===================== + +(cell, tuple) _initialize(cell $md) impure inline { + (cell $storage, tuple actions) = preamble(); + cell $path = $storage.cl::get(Channel::path); + + throw_if( + Channel::ERROR::wrongPath, + ($path.cl::get(lz::Path::srcEid) == 0) + | ($path.cl::get
(lz::Path::srcOApp) == NULLADDRESS) + | ($path.cl::get(lz::Path::dstEid) == 0) + | ($path.cl::get
(lz::Path::dstOApp) == NULLADDRESS) + ); + + return ( + $storage + .cl::set(Channel::executionQueue, DeterministicInsertionCircularQueue::create()) + .cl::set(Channel::sendRequestQueue, DeterministicInsertionCircularQueue::create()), + actions + ); +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _assertEndpoint() impure inline { + throw_unless( + Channel::ERROR::onlyEndpoint, + getCaller() == getContractStorage().Channel::getEndpointAddress() + ); +} + +;; this function is purposely designed to be maximally efficient when using a +;; custom configuration and less efficient when using a default configuration +() _assertSendMsglib(cell $mdMsglibSendCallback) impure inline { + ;; Resolve the actual sendMsglib address at the time of request. + ;; This function assumes the messagelib is not malicious or man-in-the-middle attacking, + ;; as those cases are asserted in the handler itself. + int sendMsglibAddress = $mdMsglibSendCallback + .md::MsglibSendCallback::getLzSend() + .md::LzSend::getSendMsglib(); + + throw_unless(Channel::ERROR::onlyApprovedSendMsglib, getCaller() == sendMsglibAddress); +} + +() _assertOApp() impure inline { + throw_unless( + Channel::ERROR::onlyOApp, + getCaller() == getContractStorage() + .Channel::getPath() + .lz::Path::getSrcOApp() + ); +} + +() _checkPermissions(int op, cell $md) impure inline { + if (op == Channel::OP::LZ_RECEIVE_PREPARE) { + ;; open and public calls + return (); + } elseif ( + (op == Channel::OP::CHANNEL_SEND) + | (op == Channel::OP::CHANNEL_COMMIT_PACKET) + ) { + return _assertEndpoint(); + } elseif (op == Channel::OP::MSGLIB_SEND_CALLBACK) { + return _assertSendMsglib($md); + } elseif ( + (op == Channel::OP::LZ_RECEIVE_LOCK) + | (op == Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK) + ) { + return _assertOApp(); + } elseif (op == Channel::OP::DEPOSIT_ZRO) { + return assertOwner(); + } elseif ( + (op == Channel::OP::NOTIFY_PACKET_EXECUTED) + | (op == Channel::OP::SYNC_MSGLIB_CONNECTION) + ) { + return (); + } elseif (op == Channel::OP::SET_EP_CONFIG_OAPP) { + return _assertEndpoint(); + } elseif ( + ;; Management functions are all gated by OApp + (op == Channel::OP::NILIFY) + | (op == Channel::OP::BURN) + | (op == Channel::OP::FORCE_ABORT) + ) { + return _assertOApp(); + } elseif (op == Channel::OP::EMIT_LZ_RECEIVE_ALERT) { + return (); + } else { + ;; we must put a check for all opcodes to make sure we don't + ;; mistakenly miss an opp code's permissions + throw(BaseInterface::ERROR::invalidOpcode); + } +} + +;;; ==========================HANDLERS===================================== + +;; @in endpoint/handler.fc/setEpConfig +;; @out controller/handler.fc/emit_event +;; @md EpConfig +tuple setEpConfigOApp(cell $epConfigOApp) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + setContractStorage( + $storage.cl::set(Channel::epConfigOApp, $epConfigOApp.lz::EpConfig::sanitize()) + ); + + actions~pushAction(Channel::event::EP_CFG_OAPP_SET, $epConfigOApp); + return actions; +} + +;;; ========================================== +;; Send flow +;; @in: endpoint/handler.fc/quote +;; @in_md: MdObj(lzSend, defaultEpConfig) +;; @out: msglib/handler.fc/quote +;; @out_md: $lzSend +tuple channelSend(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + ( + cell $lzSend, + cell $defaultSendEpConfig + ) = $mdObj.md::MdObj::deserialize(); + + ;; assert the size and structure of the incoming lzSend message + lz::Packet::assertValidSendMessage( + $lzSend.md::LzSend::getPacket() + ); + + ( + cell $epConfigOApp, + cell $sendPath, + cell sendRequestQueue, + int lastSendRequestId + ) = $storage.Channel::getSendInformation(); + + ;; Resolve the desired send msglib and send msglib connection + ( + int isEpConfigNull, + int sendMsglibManager, + int sendMsglib, + int sendMsglibConnection + ) = $epConfigOApp.lz::EpConfig::deserializeSendConfig(); + + if (isEpConfigNull) { + (sendMsglibManager, sendMsglib, sendMsglibConnection) = $defaultSendEpConfig.lz::SendEpConfig::deserialize(); + } + + if ((sendMsglibManager == NULLADDRESS) | (sendMsglib == NULLADDRESS) | (sendMsglibConnection == NULLADDRESS)) { + actions~pushAction( + $sendPath.lz::Path::getSrcOApp(), ;; the OApp on this chain + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + $lzSend, + 0, + 0, + Channel::ERROR::MsglibBlocked + ), + getInitialStorage() + ) + ); + return actions; + } + + ;; Each send request is assigned a unique request ID, which is also used as the key into + ;; the sendRequestQueue + int curRequestId = lastSendRequestId + 1; + + $lzSend = md::LzSend::fillRequestInfo( + $lzSend, + curRequestId, + sendMsglibManager, + sendMsglib, + sendMsglibConnection + ); + + (_, _, _, int exists) = DeterministicInsertionCircularQueue::get(sendRequestQueue, curRequestId); + ifnot (exists) { + ;; submit to the msglib + setContractStorage( + $storage.Channel::setSendRequestQueueAndLastSendRequestId( + curRequestId, + DeterministicInsertionCircularQueue::set( + sendRequestQueue, + curRequestId, + _buildSendRequestQueueEntry($lzSend), + SendRequestQueue::sending + ) + ) + ); + + actions~pushAction( + sendMsglibConnection, + MsglibConnection::OP::MSGLIB_CONNECTION_SEND, + $lzSend + ); + } else { + ;; callback to the oApp with a failure and emit an event + actions~pushAction(Channel::ERROR::sendQueueCongested, $lzSend); + actions~pushAction( + $sendPath.lz::Path::getSrcOApp(), ;; the OApp on this chain + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New($lzSend, 0, 0, Channel::ERROR::sendQueueCongested), + getInitialStorage() + ) + ); + } + + return actions; +} + +;; in: msglib/handler.fc/msglibSend +;; in_md: MsglibSendCallback +;; out: OApp/handler.fc/sendCallback +tuple msglibSendCallback(cell $mdMsglibSendCallback) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + ( + int errorCode, + int nativeQuote, + int zroQuote, + cell $lzSend, + cell serializedPayees, + cell encodedPacket, + int nonceByteOffset, + int nonceBytes, + int guidByteOffset, + int guidBytes, + cell $sendEvents + ) = $mdMsglibSendCallback.md::MsglibSendCallback::deserialize(); + + ( + int requestId, + int lzSendNativeFee, + int lzSendZroFee, + cell $extraOptions, + cell $enforceOptions, + int sendMsglibManager + ) = $lzSend.md::LzSend::deserializeSendCallback(); + + ( + cell sendRequestQueue, + int zroBalance, + cell $sendPath, + int outboundNonce + ) = $storage.Channel::getSendCallbackInformation(); + + ;; Read the requestId from the sendRequestQueue to ensure this send request is genuine + ;; and is not being double-executed + (_, cell contents, _, int exists) = DeterministicInsertionCircularQueue::get( + sendRequestQueue, + requestId + ); + + if (exists) { + if (_readSendRequestQueueEntry(contents) == $lzSend.cl::hash()) { + $storage = $storage.Channel::setSendRequestQueue( + DeterministicInsertionCircularQueue::delete(sendRequestQueue, requestId) + ); + } else { + ;; See below comment, this else case is logically the same as the below else block, + ;; but needs to be split due to lack of short-circuiting boolean expressions in funC + return actions; + } + } else { + ;; if the send request doesn't exist, there are two cases + ;; 1. a legitimate request was frontrun by a force-abort + ;; in this case, we can safely refund all the funds to the origin + ;; 2. a malicious MITM attack by ULN + ;; in this case, we can't refund the funds, but we can still emit an event + + ;; This technically silently reverts, by not processing any output actions, + ;; thus providing a refund, instead of hard reverting + return actions; + } + + ;; verify that cumulative fees quoted by the msglib <= the fee cap specified by the user/app + if (lzSendNativeFee < nativeQuote) { + errorCode = Channel::ERROR::notEnoughNative; + } + if (lzSendZroFee < zroQuote) { + errorCode = Channel::ERROR::notEnoughZroToken; + } + + ;; Verify that the ZRO token credits in the Channel is sufficient to cover the + ;; quoted ZRO cost of the message. + if (zroBalance < zroQuote) { + errorCode = Channel::ERROR::notEnoughZroTokenBalance; + } + + int packetGuid = 0; + int packetNonce = 0; + + if (errorCode == Channel::NO_ERROR) { + ;; Assign a nonce to the packet and calculate the resulting GUID + packetNonce = outboundNonce + 1; + packetGuid = lz::Packet::calculateGuid($sendPath, packetNonce); + + ;; native payments + tuple payees = deserializePayees(serializedPayees); + + ;; If the TON message does not contain sufficient value to perform the payments, + ;; the transaction will revert and the send channel will eventually get blocked. + ;; It is the responsibility of the OApp to assert sufficient gas + value to cover the + ;; entire transaction and avoid this failure. + repeat (payees.tlen()) { + [int payeeAddress, int nativeAmount] = payees~tpopPayee(); + actions~pushAction(payeeAddress, nativeAmount, 0); + } + + ;; Due to asynchrony between the Msglib and the Channel, the nonce and guid + ;; cannot be ... ? + + cell completedEncodedPacket = null(); + + if (guidByteOffset > nonceByteOffset) { + completedEncodedPacket = encodedPacket + .lz::Packet::replaceTwoFieldsAtOffsets( + packetNonce, + nonceByteOffset, + nonceBytes, + packetGuid, + guidByteOffset, + guidBytes + ); + } else { + completedEncodedPacket = encodedPacket + .lz::Packet::replaceTwoFieldsAtOffsets( + packetGuid, + guidByteOffset, + guidBytes, + packetNonce, + nonceByteOffset, + nonceBytes + ); + } + + actions~pushAction( + Channel::event::PACKET_SENT, + md::PacketSent::build( + nativeQuote, + zroQuote, + $extraOptions, + $enforceOptions, + completedEncodedPacket, + packetNonce, + sendMsglibManager, + $sendEvents + ) + ); + + $storage = $storage.Channel::setOutboundNonceAndZroBalance( + packetNonce, + zroBalance - zroQuote + ); + } + + ;; If the quote was unsuccessful, delete the hash from storage to prevent hol blocking + ;; If the quote was successful, additionally update the ZRO balance and outbound nonce + setContractStorage($storage); + + actions~pushAction( + $sendPath.lz::Path::getSrcOApp(), ;; the OApp on this chain + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::build( + md::MessagingReceipt::build( + $lzSend.md::LzSend::setPacketNonceAndGuid(packetNonce, packetGuid), + nativeQuote, + zroQuote, + errorCode + ), + getInitialStorage() + ) + ); + + return actions; +} + +;;; ========================================== +;; Receive flow +;; @in endpoint/handler.fc/verify +;; @in_md ExtendedMd(msglibConnectionAddress, defaultEpConfig, verify) +;; @out packet_receive/handler.fc/verify +;; @out_md ExtendedMd(msglib_addr, _, verify) +;; @out controller/handler.fc/emit_event +tuple channelCommitPacket(cell $mdExtended) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizeMdExtended = $mdExtended.md::ExtendedMd::sanitize(); + + ( + cell $packet, + int callerMsglibConnectionAddress + ) = $sanitizeMdExtended.md::ExtendedMd::getMdAndForwardingAddress(); + ;; assert the size of the incoming packet + lz::Packet::assertValidReceiveMessage($packet); + + ( + cell $epConfigOApp, + cell $commitPOOO, + cell $executePOOO, + cell executionQueue + ) = $storage.Channel::getCommitPacketInformation(); + + ( + int useDefaults, + int receiveMsglibConnection + ) = $epConfigOApp.lz::EpConfig::deserializeReceiveConfig(); + + if (useDefaults) { + cell $defaultConfig = $sanitizeMdExtended.md::ExtendedMd::getObj(); + receiveMsglibConnection = $defaultConfig.lz::ReceiveEpConfig::getReceiveMsglibConnection(); + } + + if (receiveMsglibConnection != callerMsglibConnectionAddress) { + ;; grossly inefficient, but this will (almost) never happen + ;; so we can optimize the happy path by isolating this logic into this block + cell $defaultConfig = $sanitizeMdExtended.cl::get(md::MdObj::obj); + int timeoutReceiveMsglibConnection = useDefaults + ? $defaultConfig.cl::get
(lz::ReceiveEpConfig::timeoutReceiveMsglibConnection) + : $epConfigOApp.cl::get
(lz::EpConfig::timeoutReceiveMsglibConnection); + + int expiry = useDefaults + ? $defaultConfig.cl::get(lz::ReceiveEpConfig::expiry) + : $epConfigOApp.cl::get(lz::EpConfig::timeoutReceiveMsglibExpiry); + + if ((timeoutReceiveMsglibConnection != callerMsglibConnectionAddress) | (expiry < now())) { + throw(Channel::ERROR::onlyApprovedReceiveMsglib); + } + } + + int incomingNonce = $packet.lz::Packet::getNonce(); + + int isCommittable = _optimizedNonceCommittable( + $executePOOO, + executionQueue, + incomingNonce + ); + + if (isCommittable) { + setContractStorage( + $storage + .Channel::setCommitPOOOAndExecutionQueue( + POOO::set($commitPOOO, incomingNonce), + DeterministicInsertionCircularQueue::set( + executionQueue, + incomingNonce, + $packet, + ExecutionQueue::committed + ) + ) + ); + actions~pushAction(Channel::event::PACKET_COMMITTED, $packet); + } + + if (incomingNonce <= POOO::maxSettableBit($executePOOO)) { + ;; Cannot respond back to msglib if the packet is not currently committable but + ;; will be committable in the future + ;; Caveat: if the packet is currently executing, we treat it as uncommittable. + ;; There exists a race condition where a uncommitted re-committable packet + ;; can be marked as committed. If the packet needs to be re-committed for a good reason + ;; (e.g., malicious DVN), the OApp owner must first nilify the packet + + actions~pushAction( + callerMsglibConnectionAddress, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + incomingNonce, + $storage.Channel::getExecutePOOO().POOO::getNextEmpty() + ) + ); + } + + return actions; +} + +;;; ========================================== +;; Execution step 1 +;; @in_opcode Channel::OP::LZ_RECEIVE_PREPARE +;; @in_from (external in) permissionless +;; @in_md nonce +;; @out_opcode Layerzero::OP::LZ_RECEIVE_PREPARE +;; @out_to srcOApp +;; @out_md ExtendedMd(md=packetId, obj=channel_init_state, forwarding_addr=NULLADDRESS) +;; @permissions: permissonless +tuple lzReceivePrepare(cell $lzReceivePrepareMd) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + (int nonce, int nanotons) = $lzReceivePrepareMd.md::LzReceivePrepare::deserialize(); + + ;; extract oApp from path + actions~pushAction( + $storage.Channel::getPath().lz::Path::getSrcOApp(), ;; the OApp on this chain + Layerzero::OP::LZ_RECEIVE_PREPARE, + ;; Throws if the Packet is not executable + _getExecutablePacket(nonce), + nanotons + ); + + return actions; +} + +;; @in_opcode Channel::OP::LZ_RECEIVE_LOCK +;; @in_from oApp +;; @in_md nonce +;; @out_opcode Layerzero::OP::LZ_RECEIVE_EXECUTE +;; @out_to oApp +;; @out_md ExtendedMd(md=Packet, obj=channel_init_state, forwarding_addr=NULLADDRESS) +;; @permissions: only oApp +tuple lzReceiveLock(cell $nonceMd) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + int incomingNonce = $nonceMd.md::Nonce::getNonce(); + throw_if(Channel::ERROR::invalidNonce, incomingNonce <= 0); + + ( + cell executionQueue, + cell $commitPOOO, + cell $sendPath + ) = $storage.Channel::getLzReceiveLockInformation(); + + (int actualKey, cell $packet, int status, _) = DeterministicInsertionCircularQueue::get( + executionQueue, + incomingNonce + ); + + int firstUncommittedNonce = $commitPOOO.POOO::getNextEmpty(); + + ;; executable if present and all preceding nonces are committed, executing, or executed + if ( + (actualKey == incomingNonce) + & (status == ExecutionQueue::committed) + & (incomingNonce < firstUncommittedNonce) + ) { + ;; set state to executing + setContractStorage( + $storage.Channel::setExecutionQueue( + DeterministicInsertionCircularQueue::set( + executionQueue, + incomingNonce, + $packet, + ExecutionQueue::executing + ) + ) + ); + + actions~pushAction( + $sendPath.lz::Path::getSrcOApp(), ;; the OApp on this chain + Layerzero::OP::LZ_RECEIVE_EXECUTE, + md::MdObj::build($packet, getInitialStorage()) + ); + } else { + actions~pushAction( + Channel::event::NOT_EXECUTABLE, + md::PacketId::New( + $sendPath.lz::Path::reverse(), ;; emit the receive path + incomingNonce + ) + ); + } + + return actions; +} + +;; @in_opcode Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK +;; @in_from oApp +;; @in_md LzReceiveStatus +;; @out_opcode OP::PACKET_RECEIVE_DESTROYED_CALLBACK +;; @out_to oApp +;; @out_md ExtendedMd(md=packetId, obj=pr_init_state, forwarding_addr=address_std_hashpart_null()) +;; @failure => unlock the Packet +;; @success => destroy the Packet and refund rent +;; @permissions: only oApp +tuple lzReceiveExecuteCallback(cell $lzReceiveStatus) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + ( + int lzReceiveSuccess, + int packetNonce + ) = $lzReceiveStatus.md::LzReceiveStatus::getSuccessAndNonce(); + + ( + cell $executePOOO, + cell executionQueue, + cell $sendPath + ) = $storage.Channel::getExecutePOOOAndExecutionQueueAndPath(); + + (int actualKey, cell $packet, int status, _) = DeterministicInsertionCircularQueue::get( + executionQueue, + packetNonce + ); + + throw_unless( + Channel::ERROR::notExecuting, + (actualKey == packetNonce) & (status == ExecutionQueue::executing) + ); + + ;; check for success/failure + if (lzReceiveSuccess) { + executionQueue = DeterministicInsertionCircularQueue::delete(executionQueue, packetNonce); + + $storage = $storage.Channel::setExecutePOOO( + POOO::set($executePOOO, packetNonce) + ); + + ;; emit Packet in the manager + actions~pushAction( + Channel::event::DELIVERED, + md::PacketId::build( + $sendPath.lz::Path::optimizedReverse(), ;; emit the receive path + packetNonce + ) + ); + } else { + executionQueue = DeterministicInsertionCircularQueue::set( + executionQueue, + packetNonce, + $packet, ;; same packet object that we extracted from the queue + ExecutionQueue::committed + ); + + ;; emit Packet so we know its unlocked + actions~pushAction( + Channel::event::LZ_RECEIVE_ALERT, + md::LzReceiveStatus::NewFull( + false, + packetNonce, ;; unforgeable + $lzReceiveStatus.cl::get(md::LzReceiveStatus::value), ;; can be arbitrary/unsafe + $lzReceiveStatus.cl::get(md::LzReceiveStatus::extraData), ;; can be arbitrary/unsafe + $lzReceiveStatus.cl::get(md::LzReceiveStatus::reason), ;; can be arbitrary/unsafe + getOrigin(), ;; unforgeable + $packet, ;; unforgeable + ExecutionStatus::executable + ) + ); + } + + setContractStorage($storage.Channel::setExecutionQueue(executionQueue)); + + return actions; +} + +;;; ====================== Management Helper =================================== +() _commitFakePacket(cell $storage, int nonce, cell $receivePath) impure inline method_id { + cell $mockPacket = lz::Packet::New($receivePath, empty_cell(), nonce); + + ;; Because this is not originating from the endpoint, we dont have the defaults + ;; Actual defaults and the msglib address arent required because the call is direct from the OApp + cell $mockEpConfigDefaults = lz::ReceiveEpConfig::New( + 0xdeadbeef, ;; any non-null dummy value for the receive msglib connection address + NULLADDRESS, ;; timeout never has to be used for burn + 0 ;; as above, timeout never has to be used for burn + ); + cell $epConfigOApp = $storage.cl::get(Channel::epConfigOApp); + + ;; Step 1: Commit the 'mockPacket' + ;; This is safe because we are going to do the following steps (2 and 3) atomically. + ;; channelCommitPacket will not revert if the packet is not committed, but lzReceiveLock will. + ;; Basically lying to channelCommitPacket to say the "correct" msglib is committing + channelCommitPacket( + md::ExtendedMd::New( + $mockPacket, + $mockEpConfigDefaults, ;; this is completely ignored if useDefaults is false + $epConfigOApp.cl::get(lz::EpConfig::isNull) + ? $mockEpConfigDefaults.cl::get
(lz::ReceiveEpConfig::receiveMsglibConnection) + : $epConfigOApp.cl::get
(lz::EpConfig::receiveMsglibConnection) + ) + ); +} + +;; @permissions only-oApp +tuple nilify(cell $packetId) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + $packetId = $packetId.md::PacketId::sanitize(); + + ;; reverse the path because this is from a receive perspective + cell $receivePath = $storage.Channel::getPath().lz::Path::reverse(); + _assertEqualPaths($receivePath, $packetId.cl::get(md::PacketId::path)); + + int incomingNonce = $packetId.cl::get(md::PacketId::nonce); + + (int isCommittable, cell $previousPacket) = _nonceCommittable(incomingNonce); + throw_unless(Channel::ERROR::notCommittable, isCommittable); + + _commitFakePacket($storage, incomingNonce, $receivePath); + + setContractStorage( + getContractStorage().cl::set( + Channel::executionQueue, + DeterministicInsertionCircularQueue::delete( + $storage.Channel::getExecutionQueue(), + incomingNonce + ) + ) + ); + + if ($previousPacket.is_null()) { + $previousPacket = lz::Packet::New($receivePath, empty_cell(), incomingNonce); + } + actions~pushAction(Channel::event::PACKET_NILIFIED, $previousPacket); + + actions~pushAction( + $receivePath.cl::get
(lz::Path::dstOApp), ;; the OApp on this chain + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::build($packetId, getInitialStorage()) + ); + + return actions; +} + +tuple burn(cell $packetId) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $packetId = $packetId.md::PacketId::sanitize(); + + ;; reverse the path because this is from a receive perspective + cell $receivePath = $storage.Channel::getPath().lz::Path::reverse(); + _assertEqualPaths($receivePath, $packetId.cl::get(md::PacketId::path)); + + int nonce = $packetId.cl::get(md::PacketId::nonce); + + cell $nonceMd = md::Nonce::New(nonce); + + (_, cell $previousPacket) = _nonceCommittable(nonce); + + ;; Step 1: Commit a 'mockPacket' to be used when we 'burn' this nonce + _commitFakePacket($storage, nonce, $receivePath); + + ;; Step 2: Put the packet into 'executing' + lzReceiveLock($nonceMd); + ;; Step 3: Mock the lzReceiveExecuteCallback, which marks/flags that given nonce as used and 'executed' + lzReceiveExecuteCallback(md::LzReceiveStatus::New(true, nonce)); + + + if ($previousPacket.is_null()) { + $previousPacket = lz::Packet::New($receivePath, empty_cell(), nonce); + } + + ;; Emit an event so we are able to observe offchain that this nonce has been 'burned' + actions~pushAction( + Channel::event::PACKET_BURNED, + $previousPacket + ); + + actions~pushAction( + $receivePath.cl::get
(lz::Path::dstOApp), ;; the OApp on this chain + Layerzero::OP::BURN_CALLBACK, + md::MdObj::build( + md::PacketId::New($receivePath, nonce), + getInitialStorage() + ) + ); + + return actions; +} + +;;; ========================================== +;; ZRO management +;; only controller +tuple depositZro(cell $coinsAmount) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedCoinsAmount = $coinsAmount.md::CoinsAmount::sanitize(); + + setContractStorage( + $storage.cl::set( + Channel::zroBalance, + $storage.Channel::getZroBalance() + + $sanitizedCoinsAmount.cl::get(md::CoinsAmount::amount) + ) + ); + + actions~pushAction(Channel::event::ZRO_DEPOSITED, $sanitizedCoinsAmount); + + return actions; +} + +;; Attempt to abort a send request. Check if hash still present, if present delete and send +;; @in: oApp +;; @in_opcode Channel::OP::FORCE_ABORT +;; @in_md lzSend +;; @out_opcode +;; @out_to oApp +;; @out_md lzSend +;; @permissions: only oApp +tuple forceAbort(cell $lzSend) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sendPath = $storage.cl::get(Channel::path); + ;; $lzSend does not need to be sanitized, as it must be correct to match + ;; the stored hash + _assertEqualPaths( + $sendPath, + $lzSend.md::LzSend::getPath() + ); + + int requestId = $lzSend.md::LzSend::getSendRequestId(); + + cell sendRequestQueue = $storage.cl::get(Channel::sendRequestQueue); + + (_, cell request, int status, _) = DeterministicInsertionCircularQueue::get(sendRequestQueue, requestId); + + throw_if( + Channel::ERROR::cannotAbortSend, + (status != SendRequestQueue::sending) | (_readSendRequestQueueEntry(request) != $lzSend.cl::hash()) + ); + + ;; delete the reservation and update the storage + setContractStorage( + $storage.cl::set( + Channel::sendRequestQueue, + DeterministicInsertionCircularQueue::delete(sendRequestQueue, requestId) + ) + ); + + actions~pushAction( + $sendPath.cl::get
(lz::Path::srcOApp), ;; the OApp on this chain + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New($lzSend, 0, 0, Channel::ERROR::sendAborted), + getInitialStorage() + ) + ); + + return actions; +} + +;; Send the current state of the channel to the MsglibConnection +;; @in: permissionless +;; @in_opcode Channel::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE +;; @in_md mdAddress ( MsglibConnectionAddress, Path ) +tuple syncMsglibConnection(cell $mdAddress) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedMdAddress = $mdAddress.md::MdAddress::sanitize(); + + actions~pushAction( + $sanitizedMdAddress.cl::get
(md::MdAddress::address), ;; msglibConnectionAddress + MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE, + md::MdObj::New( + md::ChannelNonceInfo::New( + $storage + .cl::get(Channel::commitPOOO) + .cl::get(POOO::nextEmpty), + $storage + .cl::get(Channel::executePOOO) + .cl::get(POOO::nextEmpty) + ), + getInitialStorage() + ) + ); + + return actions; +} + +tuple notifyPacketExecuted(cell $mdAddress) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedMdAddress = $mdAddress.md::MdAddress::sanitize(); + cell $sanitizedNonceMd = $sanitizedMdAddress + .cl::get(md::MdAddress::md) + .md::Nonce::sanitize(); + + int executionStatus = _viewExecutionStatus($sanitizedNonceMd.cl::get(md::Nonce::nonce)); + + if (executionStatus != ExecutionStatus::executed) { + return actions; + } + + actions~pushAction( + $sanitizedMdAddress.cl::get
(md::MdAddress::address), + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + $sanitizedNonceMd.cl::get(md::Nonce::nonce), + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ); + + return actions; +} + +tuple emitLzReceiveAlert(cell $lzReceiveStatus) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $saniztizedLzReceiveStatus = $lzReceiveStatus.md::LzReceiveStatus::NewFull::sanitize(); + + int nonce = $saniztizedLzReceiveStatus.cl::get(md::LzReceiveStatus::nonce); + throw_if(Channel::ERROR::invalidNonce, nonce == 0); + + (int actualNonce, cell $packet, _, int exists) = DeterministicInsertionCircularQueue::get( + $storage.Channel::getExecutionQueue(), + nonce + ); + + throw_unless( + Channel::ERROR::invalidNonce, + (actualNonce == nonce) & (exists) + ); + + actions~pushAction( + Channel::event::LZ_RECEIVE_ALERT, + md::LzReceiveStatus::NewFull( + $saniztizedLzReceiveStatus.cl::get(md::LzReceiveStatus::success), + nonce, + $saniztizedLzReceiveStatus.cl::get(md::LzReceiveStatus::value), + $saniztizedLzReceiveStatus.cl::get(md::LzReceiveStatus::extraData), + $saniztizedLzReceiveStatus.cl::get(md::LzReceiveStatus::reason), + getCaller(), + $packet, + _viewExecutionStatus(nonce) + ) + ); + return actions; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/interface.fc new file mode 100644 index 00000000..8058328a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/interface.fc @@ -0,0 +1,61 @@ +#include "callbackOpcodes.fc"; + +;; Opcodes +const int Channel::OP::SET_EP_CONFIG_OAPP = "Channel::OP::SET_EP_CONFIG_OAPP"c; +const int Channel::OP::MSGLIB_SEND_CALLBACK = "Channel::OP::MSGLIB_SEND_CALLBACK"c; +const int Channel::OP::CHANNEL_SEND = "Channel::OP::CHANNEL_SEND"c; +const int Channel::OP::CHANNEL_COMMIT_PACKET = "Channel::OP::CHANNEL_COMMIT_PACKET"c; +const int Channel::OP::LZ_RECEIVE_PREPARE = "Channel::OP::LZ_RECEIVE_PREPARE"c; +const int Channel::OP::DEPOSIT_ZRO = "Channel::OP::DEPOSIT_ZRO"c; +const int Channel::OP::NILIFY = "Channel::OP::NILIFY"c; +const int Channel::OP::BURN = "Channel::OP::BURN"c; +const int Channel::OP::FORCE_ABORT = "Channel::OP::FORCE_ABORT"c; +const int Channel::OP::LZ_RECEIVE_LOCK = "Channel::OP::LZ_RECEIVE_LOCK"c; +const int Channel::OP::SYNC_MSGLIB_CONNECTION = "Channel::OP::SYNC_MSGLIB_CONNECTION"c; +const int Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK = "Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK"c; +const int Channel::OP::NOTIFY_PACKET_EXECUTED = "Channel::OP::NOTIFY_PACKET_EXECUTED"c; +const int Channel::OP::EMIT_LZ_RECEIVE_ALERT = "Channel::OP::EMIT_LZ_RECEIVE_ALERT"c; + +;; EVENTS +const int Channel::event::EP_CFG_OAPP_SET = "Channel::event::EP_CFG_OAPP_SET"u; +const int Channel::event::PACKET_SENT = "Channel::event::PACKET_SENT"u; +const int Channel::event::PACKET_COMMITTED = "Channel::event::PACKET_COMMITTED"u; +const int Channel::event::PACKET_NILIFIED = "Channel::event::PACKET_NILIFIED"u; +const int Channel::event::PACKET_BURNED = "Channel::event::PACKET_BURNED"u; +const int Channel::event::DELIVERED = "Channel::event::DELIVERED"u; +const int Channel::event::LZ_RECEIVE_ALERT = "Channel::event::LZ_RECEIVE_ALERT"u; +const int Channel::event::NOT_EXECUTABLE = "Channel::event::NOT_EXECUTABLE"u; +const int Channel::event::ZRO_DEPOSITED = "Channel::event::ZRO_DEPOSITED"u; + +;; ERRORS +const int Channel::ERROR::onlyEndpoint = 129; +const int Channel::ERROR::onlyOApp = 130; +const int Channel::ERROR::onlyApprovedSendMsglib = 131; +const int Channel::ERROR::onlyApprovedReceiveMsglib = 132; +const int Channel::ERROR::invalidNonce = 133; +const int Channel::ERROR::cannotAbortSend = 134; +const int Channel::ERROR::sendAborted = 135; +const int Channel::ERROR::notEnoughNative = 136; +const int Channel::ERROR::notEnoughZroToken = 137; +const int Channel::ERROR::sendQueueCongested = 138; +const int Channel::ERROR::notEnoughZroTokenBalance = 139; +const int Channel::ERROR::notCommittable = 140; +const int Channel::ERROR::notExecutable = 141; +const int Channel::ERROR::notExecuting = 142; +const int Channel::ERROR::wrongPath = 143; +const int Channel::ERROR::MsglibBlocked = 144; +const int Channel::NO_ERROR = 0; + +;; States for view function and packet executability management +const int ExecutionStatus::uncommitted = 0; +const int ExecutionStatus::committedNotExecutable = 1; +const int ExecutionStatus::executable = 2; +const int ExecutionStatus::executed = 3; +const int ExecutionStatus::executing = 4; ;; new state +const int ExecutionStatus::committed = 8; ;; only used internally + +const int ExecutionQueue::uncommitted = 0; +const int ExecutionQueue::executing = 1; +const int ExecutionQueue::committed = 2; + +const int SendRequestQueue::sending = 1; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/main.fc new file mode 100644 index 00000000..5d100144 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/main.fc @@ -0,0 +1,39 @@ +#include "../core/abstract/protocolMain.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == Channel::OP::SET_EP_CONFIG_OAPP) { + return setEpConfigOApp($md); + } elseif (op == Channel::OP::CHANNEL_SEND) { + return channelSend($md); + } elseif (op == Channel::OP::MSGLIB_SEND_CALLBACK) { + return msglibSendCallback($md); + } elseif (op == Channel::OP::CHANNEL_COMMIT_PACKET) { + return channelCommitPacket($md); + } elseif (op == Channel::OP::LZ_RECEIVE_PREPARE) { + return lzReceivePrepare($md); + } elseif (op == Channel::OP::LZ_RECEIVE_LOCK) { + return lzReceiveLock($md); + } elseif (op == Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK) { + return lzReceiveExecuteCallback($md); + } elseif (op == Channel::OP::DEPOSIT_ZRO) { + return depositZro($md); + } elseif (op == Channel::OP::NILIFY) { + return nilify($md); + } elseif (op == Channel::OP::BURN) { + return burn($md); + } elseif (op == Channel::OP::FORCE_ABORT) { + return forceAbort($md); + } elseif (op == Channel::OP::SYNC_MSGLIB_CONNECTION) { + return syncMsglibConnection($md); + } elseif (op == Channel::OP::NOTIFY_PACKET_EXECUTED) { + return notifyPacketExecuted($md); + } elseif (op == Channel::OP::EMIT_LZ_RECEIVE_ALERT) { + return emitLzReceiveAlert($md); + } else { + throw(BaseInterface::ERROR::invalidOpcode); + } + return empty_tuple(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/storage.fc new file mode 100644 index 00000000..069cfac8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/storage.fc @@ -0,0 +1,273 @@ +#include "../../funC++/dataStructures/PipelinedOutOfOrder.fc"; + +#include "../../classes/lz/EpConfig.fc"; + +#include "../core/baseStorage.fc"; + +;; maximum concurrent sendable inflight send requests +;; must be low to avoid permanent bricking +const int Channel::MAX_SEND_SLOTS = MAX_CELL_BITS; + +;; required object name +const int Channel::NAME = "channel"u; + +;; field names +;; Init state (sharding key) +const int Channel::baseStorage = 0; +const int Channel::path = 1; + +;; Both send and receive channel state +const int Channel::endpointAddress = 2; +const int Channel::epConfigOApp = 3; + +;; Send channel state +const int Channel::outboundNonce = 4; +const int Channel::sendRequestQueue = 5; +const int Channel::lastSendRequestId = 6; + +;; Receive channel state +const int Channel::commitPOOO = 7; + +;; Used to track the commit verification queue / capacity +const int Channel::executePOOO = 8; +const int Channel::executionQueue = 9; + +const int Channel::zroBalance = 10; + +;; @owner manager +cell Channel::New(int owner, cell $path, int endpointAddress) impure inline method_id { + return cl::declare( + Channel::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; Channel::baseStorage + [cl::t::objRef, $path], ;; Channel::path + [cl::t::address, endpointAddress], ;; Channel::endpointAddress + [cl::t::objRef, lz::EpConfig::NewWithDefaults()], ;; Channel::epConfigOApp + [cl::t::uint64, 0], ;; Channel::outboundNonce + [cl::t::objRef, cl::nullObject()], ;; Channel::sendRequestQueue (DICQ) + [cl::t::uint64, 0], ;; Channel::sendRequestId + [cl::t::objRef, POOO::New()], ;; Channel::commitPOOO + [cl::t::objRef, POOO::New()], ;; Channel::executePOOO + [cl::t::cellRef, cl::nullObject()], ;; Channel::executionQueue (DICQ) + [cl::t::coins, 0] ;; Channel::zroBalance + ]) + ); +} + +;; ====================== Object Accessors ===================== + +const int Channel::_endpointAddressOffset = _HEADER_WIDTH; +const int Channel::_outboundNonceOffset = Channel::_endpointAddressOffset + 256; +const int Channel::_sendRequestIdOffset = Channel::_outboundNonceOffset + 64; +const int Channel::_zroBalanceOffset = Channel::_sendRequestIdOffset + 64; +const int Channel::_sliceBits = Channel::_zroBalanceOffset + 128; + +cell Channel::getBaseStorage(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +cell Channel::getPath(cell $self) impure inline { + return $self.cellPreloadRefAt(1); +} + +int Channel::getEndpointAddress(cell $self) impure inline { + return $self.cellPreloadAddressAt(Channel::_endpointAddressOffset); +} + +cell Channel::getCommitPOOO(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(2); +} + +cell Channel::getExecutePOOO(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(3); +} + +cell Channel::getExecutionQueue(cell $self) impure inline { + return $self.cellPreloadRefAt(3).cellPreloadRefAt(0); +} + +int Channel::getZroBalance(cell $self) impure inline { + return $self.cellPreloadCoinsAt(Channel::_zroBalanceOffset); +} + +;; (epConfigOApp, commitPOOO, ExecutePOOO, executionQueue) +(cell, cell, cell, cell) Channel::getCommitPacketInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2 = selfSlice.preloadRefSliceAt(2); + return ( + ref2.preloadRefAt(0), + ref2.preloadRefAt(2), + ref2.preloadRefAt(3), + selfSlice.preloadRefAt(3).cellPreloadRefAt(0) + ); +} + +;; (executePOOO, executionQueue, path) +(cell, cell, cell) Channel::getExecutePOOOAndExecutionQueueAndPath(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(2).cellPreloadRefAt(3), + selfSlice.preloadRefAt(3).cellPreloadRefAt(0), + selfSlice.preloadRefAt(1) + ); +} + +;; (epConfigOapp, path, sendRequestQueue, lastSendRequestId) +(cell, cell, cell, int) Channel::getSendInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2 = selfSlice.preloadRefSliceAt(2); + return ( + ref2.preloadRefAt(0), + selfSlice.preloadRefAt(1), + ref2.preloadRefAt(1), + selfSlice.preloadUint64At(Channel::_sendRequestIdOffset) + ); +} + +;; (sendRequestQueue, zroBalance, path, outBoundNonce) +(cell, int, cell, int) Channel::getSendCallbackInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(2).cellPreloadRefAt(1), ;; sendRequestQueue + selfSlice.preloadCoinsAt(Channel::_zroBalanceOffset), ;; zroBalance + selfSlice.preloadRefAt(1), ;; path + selfSlice.preloadUint64At(Channel::_outboundNonceOffset) ;; outboundNonce + ); +} + +;; (executionQueue, commitPOOO, path) +(cell, cell, cell) Channel::getLzReceiveLockInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(3).cellPreloadRefAt(0), ;; executionQueue + selfSlice.preloadRefAt(2).cellPreloadRefAt(2), ;; commitPOOO + selfSlice.preloadRefAt(1) ;; path + ); +} + +;; ====================== Object Modifiers ===================== + +cell Channel::setSendRequestQueue(cell $self, cell $sendRequestQueue) impure inline { + slice selfSlice = $self.begin_parse(); + + slice ref2Slice = selfSlice.preloadRefSliceAt(2); + cell newRef2 = begin_cell() + .store_slice(ref2Slice.scutfirst(0, 1)) + .store_ref($sendRequestQueue) + .store_slice(ref2Slice.scutlast(0, 2)) + .end_cell(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(Channel::_sliceBits, 2)) ;; store all the bits and the first 2 refs [0, 1] + .store_ref(newRef2) ;; store the new ref[2] which includes the new sendRequestQueue + .store_slice(selfSlice.scutlast(0, 1)) ;; store the last ref, ref[3] + .end_cell(); +} + +cell Channel::setExecutePOOO(cell $self, cell $executePOOO) impure inline { + slice selfSlice = $self.begin_parse(); + + slice ref2Slice = selfSlice.preloadRefSliceAt(2); + cell newRef2 = begin_cell() + .store_slice(ref2Slice.scutfirst(0, 3)) + .store_ref($executePOOO) + .end_cell(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(Channel::_sliceBits, 2)) ;; store all the bits and the first 2 refs [0, 1] + .store_ref(newRef2) ;; store the new ref[2] which includes the new executePOOO + .store_slice(selfSlice.scutlast(0, 1)) ;; store the last ref, ref[3] + .end_cell(); +} + +cell Channel::setExecutionQueue(cell $self, cell $executionQueue) impure inline { + slice selfSlice = $self.begin_parse(); + + cell newRef3 = begin_cell() + .store_ref($executionQueue) + .end_cell(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(Channel::_sliceBits, 3)) ;; store all the bits and the first 3 refs [0, 1, 2] + .store_ref(newRef3) ;; store the new ref[3] which includes the new executionQueue + .end_cell(); +} + +;; ====================== Object Composite Modifiers ===================== + +cell Channel::setSendRequestQueueAndLastSendRequestId(cell $self, int lastSendRequestId, cell $sendRequestQueue) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2Slice = selfSlice.preloadRefSliceAt(2); + + cell newRef2 = begin_cell() + .store_slice(ref2Slice.scutfirst(0, 1)) + .store_ref($sendRequestQueue) + .store_slice(ref2Slice.scutlast(0, 2)) + .end_cell(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(Channel::_sendRequestIdOffset, 2)) ;; store all the bits before the lastSendRequestId and the first 2 refs [0, 1] + .store_uint64(lastSendRequestId) ;; store the new lastSendRequestId = ref[2] + .store_ref(newRef2) ;; store the new ref[2] which includes the new sendRequestQueue + .store_slice(selfSlice.sskipfirst(Channel::_sendRequestIdOffset + 64, 3)) ;; store the whatever was after the lastSendRequestId and the last ref, only giving back ref[3] + .end_cell(); +} + +cell Channel::setOutboundNonceAndZroBalance(cell $self, int outboundNonce, int zroBalance) impure inline { + slice selfSlice = $self.begin_parse(); + + return begin_cell() + .store_slice( + selfSlice.scutfirst(Channel::_outboundNonceOffset, 4) + ) ;; store whatever's behind the outbound nonce and all the refs + .store_uint64(outboundNonce) + .store_slice( + selfSlice.subslice( + Channel::_sendRequestIdOffset, ;; start bits + 0, ;; start refs + 64, ;; bits + 0 ;; refs + ) + ) ;; store the next 64 bits = sendRequestId + .store_uint128(zroBalance) + .end_cell(); +} + +cell Channel::setCommitPOOOAndExecutionQueue(cell $self, cell $commitPOOO, cell $executionQueue) impure inline { + slice selfSlice = $self.begin_parse(); + + slice ref2Slice = selfSlice.preloadRefSliceAt(2); + cell newRef2 = begin_cell() + .store_slice(ref2Slice.scutfirst(0, 2)) ;; store the first 2 refs [0, 1] + .store_ref($commitPOOO) ;; store the new commitPOOO = ref[2] + .store_slice(ref2Slice.scutlast(0, 1)) ;; store the last ref, ref[3] + .end_cell(); + + cell newRef3 = begin_cell() + .store_ref($executionQueue) ;; store the new executionQueue = ref[0] + .end_cell(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(Channel::_sliceBits, 2)) ;; store all the bits and the first 3 refs [0, 1] + .store_ref(newRef2) ;; store the new ref[2] which includes the new commitPOOO + .store_ref(newRef3) ;; store the new ref[3] which includes the new executionQueue + .end_cell(); +} + +cell Channel::setPath(cell $self, cell $path) impure inline { + slice selfSlice = $self.begin_parse(); + return begin_cell() + .store_ref(selfSlice.preloadRefAt(0)) + .store_ref($path) ;; change ref 1 + .store_slice(sskipfirst(selfSlice, 0, 2)) ;; rest of it stays the same + .end_cell(); +} + +cell Channel::sanitize(cell $self) impure inline { + cell $baseStorage = $self.cl::get(Channel::baseStorage); + return Channel::New( + $baseStorage.cl::get
(BaseStorage::owner), + $self.cl::get(Channel::path), + $self.cl::get
(Channel::endpointAddress) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelBurn.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelBurn.fc new file mode 100644 index 00000000..4599498b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelBurn.fc @@ -0,0 +1,270 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/MdObj.fc"; +#include "../../../classes/msgdata/PacketId.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::burn"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(false)); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) burn::success::uncommittedNonce(cell $storage) impure { + int nonceToBurn = 1; ;; this hasnt been committed previously + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_RECEIVE_PATH(), nonceToBurn); + cell $packetToBurn = lz::Packet::New( + MOCK_RECEIVE_PATH(), + empty_cell(), + nonceToBurn + ); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell $executePOOO = $storage.cl::get(Channel::executePOOO); + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + nonceToBurn + ); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonceToBurn)) + .cl::set(Channel::executePOOO, POOO::set($executePOOO, nonceToBurn)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + burn, + $packetIdToBurn, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_BURNED, + $packetToBurn + ), + _newAction( + SRC_OAPP, ;; In this test/context the src IS the dst + Layerzero::OP::BURN_CALLBACK, + md::MdObj::New($packetIdToBurn, getInitialStorage()) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) burn::success::alreadyCommittedNonce(cell $storage) impure { + int initialNonce = 1; + int nonceToBurn = initialNonce; + + ;; packets + cell $packet = MOCK_SEND_PACKET_WITH_NONCE(initialNonce); + + ;; mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; commit the packet + channelCommitPacket($mdExtended); + ;; get current state after packet has been committed + $storage = getContractStorage(); + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_RECEIVE_PATH(), nonceToBurn); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell $executePOOO = $storage.cl::get(Channel::executePOOO); + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + nonceToBurn + ); + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonceToBurn)) + .cl::set(Channel::executePOOO, POOO::set($executePOOO, nonceToBurn)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + burn, + $packetIdToBurn, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_BURNED, + $packet + ), + _newAction( + SRC_OAPP, ;; In this test/context the src IS the dst + Layerzero::OP::BURN_CALLBACK, + md::MdObj::New($packetIdToBurn, getInitialStorage()) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) burn::success::multipleNonces(cell $storage) impure { + int initialNonce = 1; + int nonceToBurn = 4; + + ;; packets + cell $packet1 = MOCK_SEND_PACKET_WITH_NONCE(initialNonce); + cell $packet2 = MOCK_SEND_PACKET_WITH_NONCE(initialNonce + 1); + cell $packet3 = MOCK_SEND_PACKET_WITH_NONCE(initialNonce + 2); + + ;; mock input 1 + cell $mdExtended1 = md::ExtendedMd::New( + $packet1, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + ;; mock input 2 + cell $mdExtended2 = md::ExtendedMd::New( + $packet2, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + ;; mock input 3 + cell $mdExtended3 = md::ExtendedMd::New( + $packet3, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; commit the first 3 packets/nonces + channelCommitPacket($mdExtended1); + channelCommitPacket($mdExtended2); + channelCommitPacket($mdExtended3); + + ;; get current state after 3 packets have been committed + $storage = getContractStorage(); + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_RECEIVE_PATH(), nonceToBurn); + cell $packetToBurn = lz::Packet::New( + MOCK_RECEIVE_PATH(), + empty_cell(), + nonceToBurn + ); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell $executePOOO = $storage.cl::get(Channel::executePOOO); + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + nonceToBurn + ); + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonceToBurn)) + .cl::set(Channel::executePOOO, POOO::set($executePOOO, nonceToBurn)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + burn, + $packetIdToBurn, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_BURNED, + $packetToBurn + ), + _newAction( + SRC_OAPP, ;; In this test/context the src IS the dst + Layerzero::OP::BURN_CALLBACK, + md::MdObj::New($packetIdToBurn, getInitialStorage()) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) burn::revert::nonceNotExecutable(cell $storage) impure { + int nonceToBurn = NONCE; ;; this hasnt been committed previously + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_RECEIVE_PATH(), nonceToBurn); + + return test::handler::shouldFail( + burn, + $packetIdToBurn, + Channel::ERROR::notExecuting + ); +} + +(int, slice) burn::revert::wrongPath(cell $storage) impure { + int nonceToBurn = NONCE; ;; this hasnt been committed previously + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_SEND_PATH(), nonceToBurn); + + return test::handler::shouldFail( + burn, + $packetIdToBurn, + Channel::ERROR::wrongPath + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- initialize + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- burn + .tpush([burn::success::uncommittedNonce, "burn::success::uncommittedNonce"]) + .tpush([burn::success::alreadyCommittedNonce, "burn::success::alreadyCommittedNonce"]) + .tpush([burn::success::multipleNonces, "burn::success::multipleNonces"]) + .tpush([burn::revert::nonceNotExecutable, "burn::revert::nonceNotExecutable"]) + .tpush([burn::revert::wrongPath, "burn::revert::wrongPath"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelBurnDefaultConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelBurnDefaultConfig.fc new file mode 100644 index 00000000..0db3f6ed --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelBurnDefaultConfig.fc @@ -0,0 +1,269 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/MdObj.fc"; +#include "../../../classes/msgdata/PacketId.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::burn"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) burn::success::uncommittedNonce(cell $storage) impure { + int nonceToBurn = 1; ;; this hasnt been committed previously + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_RECEIVE_PATH(), nonceToBurn); + cell $packetToBurn = lz::Packet::New( + MOCK_RECEIVE_PATH(), + empty_cell(), + nonceToBurn + ); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell $executePOOO = $storage.cl::get(Channel::executePOOO); + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + nonceToBurn + ); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonceToBurn)) + .cl::set(Channel::executePOOO, POOO::set($executePOOO, nonceToBurn)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + burn, + $packetIdToBurn, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_BURNED, + $packetToBurn + ), + _newAction( + SRC_OAPP, ;; In this test/context the src IS the dst + Layerzero::OP::BURN_CALLBACK, + md::MdObj::New($packetIdToBurn, getInitialStorage()) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) burn::success::alreadyCommittedNonce(cell $storage) impure { + int initialNonce = 1; + int nonceToBurn = initialNonce; + + ;; packets + cell $packet = MOCK_SEND_PACKET_WITH_NONCE(initialNonce); + + ;; mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; commit the packet + channelCommitPacket($mdExtended); + ;; get current state after packet has been committed + $storage = getContractStorage(); + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_RECEIVE_PATH(), nonceToBurn); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell $executePOOO = $storage.cl::get(Channel::executePOOO); + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + nonceToBurn + ); + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonceToBurn)) + .cl::set(Channel::executePOOO, POOO::set($executePOOO, nonceToBurn)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + burn, + $packetIdToBurn, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_BURNED, + $packet + ), + _newAction( + SRC_OAPP, ;; In this test/context the src IS the dst + Layerzero::OP::BURN_CALLBACK, + md::MdObj::New($packetIdToBurn, getInitialStorage()) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) burn::success::multipleNonces(cell $storage) impure { + int initialNonce = 1; + int nonceToBurn = 4; + + ;; packets + cell $packet1 = MOCK_SEND_PACKET_WITH_NONCE(initialNonce); + cell $packet2 = MOCK_SEND_PACKET_WITH_NONCE(initialNonce + 1); + cell $packet3 = MOCK_SEND_PACKET_WITH_NONCE(initialNonce + 2); + + ;; mock input 1 + cell $mdExtended1 = md::ExtendedMd::New( + $packet1, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + ;; mock input 2 + cell $mdExtended2 = md::ExtendedMd::New( + $packet2, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + ;; mock input 3 + cell $mdExtended3 = md::ExtendedMd::New( + $packet3, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; commit the first 3 packets/nonces + channelCommitPacket($mdExtended1); + channelCommitPacket($mdExtended2); + channelCommitPacket($mdExtended3); + + ;; get current state after 3 packets have been committed + $storage = getContractStorage(); + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_RECEIVE_PATH(), nonceToBurn); + cell $packetToBurn = lz::Packet::New( + MOCK_RECEIVE_PATH(), + empty_cell(), + nonceToBurn + ); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell $executePOOO = $storage.cl::get(Channel::executePOOO); + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + nonceToBurn + ); + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonceToBurn)) + .cl::set(Channel::executePOOO, POOO::set($executePOOO, nonceToBurn)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + burn, + $packetIdToBurn, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_BURNED, + $packetToBurn + ), + _newAction( + SRC_OAPP, ;; In this test/context the src IS the dst + Layerzero::OP::BURN_CALLBACK, + md::MdObj::New($packetIdToBurn, getInitialStorage()) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) burn::revert::nonceNotExecutable(cell $storage) impure { + int nonceToBurn = NONCE; ;; this hasnt been committed previously + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_RECEIVE_PATH(), nonceToBurn); + + return test::handler::shouldFail( + burn, + $packetIdToBurn, + Channel::ERROR::notExecuting + ); +} + +(int, slice) burn::revert::wrongPath(cell $storage) impure { + int nonceToBurn = NONCE; ;; this hasnt been committed previously + + ;; inputs + cell $packetIdToBurn = md::PacketId::New(MOCK_SEND_PATH(), nonceToBurn); + + return test::handler::shouldFail( + burn, + $packetIdToBurn, + Channel::ERROR::wrongPath + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- initialize + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- burn + .tpush([burn::success::uncommittedNonce, "burn::success::uncommittedNonce"]) + .tpush([burn::success::alreadyCommittedNonce, "burn::success::alreadyCommittedNonce"]) + .tpush([burn::success::multipleNonces, "burn::success::multipleNonces"]) + .tpush([burn::revert::nonceNotExecutable, "burn::revert::nonceNotExecutable"]) + .tpush([burn::revert::wrongPath, "burn::revert::wrongPath"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelCommitPacket.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelCommitPacket.fc new file mode 100644 index 00000000..1f93b800 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelCommitPacket.fc @@ -0,0 +1,729 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/lz/ReceiveEpConfig.fc"; +#include "../../../classes/msgdata/ChannelNonceInfo.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/MdObj.fc"; +#include "../../../classes/msgdata/Nonce.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::CommitPacket1n"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(false)); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + + +;; ;; --------------- channel commit packet ----------------- +(int, slice) channelCommitPacket::success::nonce1(cell $storage) impure { + ;; commit the first uncommitted nonce + int nonce = 1; + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(true)); + $storage = getContractStorage(); + + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(nonce); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + nonce, + $packet, + ExecutionQueue::committed + ); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonce)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + nonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelCommitPacket::success::customReceiveLib::nonce1(cell $storage) impure { + ;; commit the first uncommitted nonce + int nonce = 1; + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(nonce); + cell $epConfigOApp = MOCK_RESOLVED_EP_CONFIG(false); + + setEpConfigOApp($epConfigOApp); + $storage = getContractStorage(); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + lz::ReceiveEpConfig::New(NULLADDRESS, NULLADDRESS, 0), + $epConfigOApp.cl::get
(lz::EpConfig::receiveMsglibConnection) + ); + + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + nonce, + $packet, + ExecutionQueue::committed + ); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonce)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + $epConfigOApp.cl::get
(lz::EpConfig::receiveMsglibConnection), + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + nonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelCommitPacket::success::customTimeoutReceiveLib::nonce1(cell $storage) impure { + ;; commit the first uncommitted nonce + int nonce = 1; + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(nonce); + cell $epConfigOApp = MOCK_RESOLVED_EP_CONFIG(false); + + setEpConfigOApp($epConfigOApp); + $storage = getContractStorage(); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + lz::ReceiveEpConfig::New(NULLADDRESS, NULLADDRESS, 0), + $epConfigOApp.cl::get
(lz::EpConfig::timeoutReceiveMsglibConnection) + ); + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + nonce, + $packet, + ExecutionQueue::committed + ); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonce)) + .cl::set(Channel::executionQueue, executionQueue); + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + $epConfigOApp.cl::get
(lz::EpConfig::timeoutReceiveMsglibConnection), + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + nonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelCommitPacket::success::nonce2(cell $storage) impure { + int nonce = 2; + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(nonce); + + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(true)); + $storage = getContractStorage(); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + nonce, + $packet, + ExecutionQueue::committed + ); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonce)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + nonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelCommitPacket::success::nonce2Helper(cell $storage) impure { + int nonce = 2; + + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(true)); + + $storage = getContractStorage(); + + commitNonce(nonce); + cell $expectedStorage = getContractStorage(); + setContractStorage($storage); + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(nonce); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + nonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelCommitPacket::success::nonce123(cell $storage) impure { + ;; commit the first uncommitted nonce + commitNonce(1); + commitNonce(2); + $storage = getContractStorage(); + commitNonce(3); + cell $expectedStorage = getContractStorage(); + setContractStorage($storage); + int nonce = 3; + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(nonce); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + nonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelCommitPacket::success::timeoutReceiveMsglib(cell $storage) impure { + int nonce = 1; + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(nonce); + + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(true)); + + $storage = getContractStorage(); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + TIMEOUT_RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + nonce, + $packet, + ExecutionQueue::committed + ); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, nonce)) + .cl::set(Channel::executionQueue, executionQueue); + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + TIMEOUT_RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + nonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelCommitPacket::success::nonceTooSmall(cell $storage) impure { + ;; try to commit an already executed nonce + int alreadyExecutedNonce = 1; + commitNonce(alreadyExecutedNonce); + lockNonce(alreadyExecutedNonce); + executeNonce(alreadyExecutedNonce); + + $storage = getContractStorage(); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + MOCK_RECEIVE_PACKET_WITH_NONCE(alreadyExecutedNonce), + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + alreadyExecutedNonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) channelCommitPacket::revert::nonceTooBig(cell $storage) impure { + ;; try to commit a nonce beyond the capacity of the queue + int tooBigNonce = MAX_CELL_BITS + 1; + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + MOCK_RECEIVE_PACKET_WITH_NONCE(tooBigNonce), + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + emptyActions(), + $storage, + txnContext + ); +} + +(int, slice) channelCommitPacket::success::nonceAlreadyExecuted(cell $storage) impure { + ;; try to commit a nonce which is within window but already executed + int alreadyExecutedNonce = 2; + commitNonce(1); + commitNonce(alreadyExecutedNonce); + lockNonce(alreadyExecutedNonce); + executeNonce(alreadyExecutedNonce); + + $storage = getContractStorage(); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + MOCK_RECEIVE_PACKET_WITH_NONCE(alreadyExecutedNonce), + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + alreadyExecutedNonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) channelCommitPacket::revert::invalidNonce(cell $storage) impure { + cell $mdExtended = md::ExtendedMd::New( + MOCK_RECEIVE_PACKET_WITH_NONCE(0), + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldFail( + channelCommitPacket, + $mdExtended, + Channel::ERROR::invalidNonce + ); +} + +(int, slice) channelCommitPacket::revert::nonceCurrentlyExecuting(cell $storage) impure { + commitNonce(1); + commitNonce(2); + lockNonce(2); + + $storage = getContractStorage(); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + MOCK_RECEIVE_PACKET_WITH_NONCE(2), + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + 2, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) channelCommitPacket::revert::wrongReceiveMsglib(cell $storage) impure { + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + MOCK_RECEIVE_PACKET_WITH_NONCE(1), + MOCK_SML_RECEIVE_EP_CONFIG(), + SEND_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldFail( + channelCommitPacket, + $mdExtended, + Channel::ERROR::onlyApprovedReceiveMsglib + ); +} + +(int, slice) channelCommitPacket::revert::expiredTimeoutReceiveMsglib(cell $storage) impure { + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(true)); + cell $mdExtended = md::ExtendedMd::New( + MOCK_RECEIVE_PACKET_WITH_NONCE(1), + MOCK_SML_RECEIVE_EP_CONFIG().cl::set(lz::ReceiveEpConfig::expiry, now() - 1), + TIMEOUT_RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldFail( + channelCommitPacket, + $mdExtended, + Channel::ERROR::onlyApprovedReceiveMsglib + ); +} + +(int, slice) channelCommitPacket::success::maxSizeMessage(cell $storage) impure { + int nextSendRequestId = $storage.cl::get(Channel::lastSendRequestId) + 1; + + cell tail = begin_cell().store_ones(MAX_CELL_WHOLE_BYTE_BITS).end_cell(); + repeat(lz::Packet::MAX_RECEIVE_MESSAGE_CELLS - 1) { + tail = begin_cell() + .store_ones(MAX_CELL_WHOLE_BYTE_BITS) + .store_ref(tail) + .end_cell(); + } + + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(true)); + $storage = getContractStorage(); + + cell $packet = MOCK_RECEIVE_PACKET_WITH_MESSAGE(tail); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + NONCE, + $packet, + ExecutionQueue::committed + ); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, NONCE)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + NONCE, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelCommitPacket::revert::messageTooBig(cell $storage) impure { + cell tail = begin_cell().store_ones(MAX_CELL_WHOLE_BYTE_BITS).end_cell(); + repeat(lz::Packet::MAX_RECEIVE_MESSAGE_CELLS) { + tail = begin_cell() + .store_ones(MAX_CELL_WHOLE_BYTE_BITS) + .store_ref(tail) + .end_cell(); + } + + cell $packet = MOCK_RECEIVE_PACKET_WITH_MESSAGE(tail); + + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldFail( + channelCommitPacket, + $mdExtended, + lz::Packet::ERROR::INVALID_MESSAGE + ); +} + +(int, slice) channelCommitPacket::revert::messageNotByteAligned(cell $storage) impure { + cell $packet = MOCK_RECEIVE_PACKET_WITH_MESSAGE(begin_cell().store_bool(true).end_cell()); + + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldFail( + channelCommitPacket, + $mdExtended, + lz::Packet::ERROR::INVALID_MESSAGE + ); +} + +(int, slice) channelCommitPacket::revert::messageNotLinkedList(cell $storage) impure { + cell innerMessageCell = begin_cell().store_ones(MAX_CELL_WHOLE_BYTE_BITS).end_cell(); + cell $packet = MOCK_RECEIVE_PACKET_WITH_MESSAGE( + begin_cell() + .store_ones(MAX_CELL_WHOLE_BYTE_BITS) + .store_ref(innerMessageCell) + .store_ref(innerMessageCell) + .end_cell() + ); + + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldFail( + channelCommitPacket, + $mdExtended, + lz::Packet::ERROR::INVALID_MESSAGE + ); +} + +(int, slice) channelCommitPacket::revert::messageInnerNodeNotFilled(cell $storage) impure { + cell tail = begin_cell().store_ones(MAX_CELL_WHOLE_BYTE_BITS).end_cell(); + cell $packet = MOCK_RECEIVE_PACKET_WITH_MESSAGE( + begin_cell() + .store_ones(MAX_CELL_WHOLE_BYTE_BITS - 8) + .store_ref(tail) + .end_cell() + ); + + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + return test::handler::shouldFail( + channelCommitPacket, + $mdExtended, + lz::Packet::ERROR::INVALID_MESSAGE + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- channel commit packet + .tpush([channelCommitPacket::success::nonce1, "channelCommitPacket::success::nonce1"]) + .tpush([channelCommitPacket::success::customReceiveLib::nonce1, "channelCommitPacket::success::customReceiveLib::nonce1"]) + .tpush([channelCommitPacket::success::customTimeoutReceiveLib::nonce1, "channelCommitPacket::success::customTimeoutReceiveLib::nonce1"]) + .tpush([channelCommitPacket::success::nonce2, "channelCommitPacket::success::nonce2"]) + .tpush([channelCommitPacket::success::nonce2Helper, "channelCommitPacket::success::nonce2Helper"]) + .tpush([channelCommitPacket::success::nonce123, "channelCommitPacket::success::nonce123"]) + .tpush([channelCommitPacket::success::timeoutReceiveMsglib, "channelCommitPacket::success::timeoutReceiveMsglib"]) + .tpush([channelCommitPacket::success::nonceTooSmall, "channelCommitPacket::success::nonceTooSmall"]) + .tpush([channelCommitPacket::revert::nonceTooBig, "channelCommitPacket::revert::nonceTooBig"]) + .tpush([channelCommitPacket::success::nonceAlreadyExecuted, "channelCommitPacket::success::nonceAlreadyExecuted"]) + .tpush([channelCommitPacket::revert::invalidNonce, "channelCommitPacket::revert::invalidNonce"]) + .tpush([channelCommitPacket::revert::nonceCurrentlyExecuting, "channelCommitPacket::revert::nonceCurrentlyExecuting"]) + .tpush([channelCommitPacket::revert::wrongReceiveMsglib, "channelCommitPacket::revert::wrongReceiveMsglib"]) + .tpush([channelCommitPacket::revert::expiredTimeoutReceiveMsglib, "channelCommitPacket::revert::expiredTimeoutReceiveMsglib"]) + .tpush([channelCommitPacket::success::maxSizeMessage, "channelCommitPacket::success::maxSizeMessage"]) + .tpush([channelCommitPacket::revert::messageTooBig, "channelCommitPacket::revert::messageTooBig"]) + .tpush([channelCommitPacket::revert::messageNotByteAligned, "channelCommitPacket::revert::messageNotByteAligned"]) + .tpush([channelCommitPacket::revert::messageNotLinkedList, "channelCommitPacket::revert::messageNotLinkedList"]) + .tpush([channelCommitPacket::revert::messageInnerNodeNotFilled, "channelCommitPacket::revert::messageInnerNodeNotFilled"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelConfig.fc new file mode 100644 index 00000000..d1573061 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelConfig.fc @@ -0,0 +1,327 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::receive"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} +;;; ===============================HELPER FUNCTIONS========================================= + +;;; ===============================TESTS========================================= + +(int, slice) setEpConfigOApp::success::basic(cell $storage) impure { + cell $epConfigMd = MOCK_RESOLVED_EP_CONFIG(false); + + cell $expectedStorage = $storage.cl::set(Channel::epConfigOApp, $epConfigMd); + + return test::handler::shouldPass( + setEpConfigOApp, + $epConfigMd, + unsafeTuple([ + 0, + _newAction( + Channel::event::EP_CFG_OAPP_SET, + $epConfigMd + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigOApp::success::SendMsglib(cell $storage) impure { + cell $expectedStorage = + $storage.cl::set( + Channel::epConfigOApp, + $storage + .cl::get(Channel::epConfigOApp) + .cl::set(lz::EpConfig::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(lz::EpConfig::sendMsglib, SEND_MSGLIB_ADDRESS) + ) + ; + + cell $epConfig = lz::EpConfig::NewWithConnection( + true, + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + 0 + ); + + return test::handler::shouldPass( + setEpConfigOApp, + $epConfig, + unsafeTuple([ + 0, + _newAction( + Channel::event::EP_CFG_OAPP_SET, + $epConfig + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigOApp::success::ReceiveMsglib(cell $storage) impure { + cell $expectedStorage = + $storage.cl::set( + Channel::epConfigOApp, + $storage + .cl::get(Channel::epConfigOApp) + .cl::set(lz::EpConfig::receiveMsglib, RECEIVE_MSGLIB_ADDRESS) + ) + ; + + cell $epConfig = lz::EpConfig::New( + true, + NULLADDRESS, + NULLADDRESS, + RECEIVE_MSGLIB_ADDRESS, + NULLADDRESS, + 0 + ); + + return test::handler::shouldPass( + setEpConfigOApp, + $epConfig, + unsafeTuple([ + 0, + _newAction( + Channel::event::EP_CFG_OAPP_SET, + $epConfig + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigOApp::success::TimeoutMsglib(cell $storage) impure { + cell $expectedStorage = + $storage.cl::set( + Channel::epConfigOApp, + $storage + .cl::get(Channel::epConfigOApp) + .cl::set(lz::EpConfig::timeoutReceiveMsglib, TIMEOUT_RECEIVE_MSGLIB_ADDRESS) + .cl::set(lz::EpConfig::timeoutReceiveMsglibExpiry, MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY()) + ) + ; + + cell $epConfig = lz::EpConfig::New( + true, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + TIMEOUT_RECEIVE_MSGLIB_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); + + return test::handler::shouldPass( + setEpConfigOApp, + $epConfig, + unsafeTuple([ + 0, + _newAction( + Channel::event::EP_CFG_OAPP_SET, + $epConfig + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigOApp::success::SendAndReceiveMsglib(cell $storage) impure { + cell $expectedStorage = + $storage.cl::set( + Channel::epConfigOApp, + $storage + .cl::get(Channel::epConfigOApp) + .cl::set(lz::EpConfig::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(lz::EpConfig::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(lz::EpConfig::receiveMsglib, RECEIVE_MSGLIB_ADDRESS) + ) + ; + + cell $epConfig = lz::EpConfig::New( + true, + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + RECEIVE_MSGLIB_ADDRESS, + NULLADDRESS, + 0 + ); + + return test::handler::shouldPass( + setEpConfigOApp, + $epConfig, + unsafeTuple([ + 0, + _newAction( + Channel::event::EP_CFG_OAPP_SET, + $epConfig + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigOApp::success::SendAndTimeoutMsglib(cell $storage) impure { + cell $expectedStorage = + $storage.cl::set( + Channel::epConfigOApp, + $storage + .cl::get(Channel::epConfigOApp) + .cl::set(lz::EpConfig::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(lz::EpConfig::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(lz::EpConfig::timeoutReceiveMsglib, TIMEOUT_RECEIVE_MSGLIB_ADDRESS) + .cl::set(lz::EpConfig::timeoutReceiveMsglibExpiry, MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY()) + ) + ; + + cell $epConfig = lz::EpConfig::New( + true, + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + NULLADDRESS, + TIMEOUT_RECEIVE_MSGLIB_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); + + return test::handler::shouldPass( + setEpConfigOApp, + $epConfig, + unsafeTuple([ + 0, + _newAction( + Channel::event::EP_CFG_OAPP_SET, + $epConfig + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigOApp::success::ReceiveAndTimeoutMsglib(cell $storage) impure { + cell $expectedStorage = + $storage.cl::set( + Channel::epConfigOApp, + $storage + .cl::get(Channel::epConfigOApp) + .cl::set(lz::EpConfig::receiveMsglib, RECEIVE_MSGLIB_ADDRESS) + .cl::set(lz::EpConfig::timeoutReceiveMsglib, TIMEOUT_RECEIVE_MSGLIB_ADDRESS) + .cl::set(lz::EpConfig::timeoutReceiveMsglibExpiry, MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY()) + ) + ; + + cell $epConfig = lz::EpConfig::New( + true, + NULLADDRESS, + NULLADDRESS, + RECEIVE_MSGLIB_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); + + return test::handler::shouldPass( + setEpConfigOApp, + $epConfig, + unsafeTuple([ + 0, + _newAction( + Channel::event::EP_CFG_OAPP_SET, + $epConfig + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigOApp::revert::invalidConfigWithSameMsglib(cell $storage) impure { + ;; form an invalid config where, receive_lib and timeoutReceiveMsglib are the same + cell $invalidEpConfigMd = MOCK_EP_CONFIG(false) + .cl::set(lz::EpConfig::timeoutReceiveMsglib, RECEIVE_MSGLIB_ADDRESS); + + return test::handler::shouldFail( + setEpConfigOApp, + $invalidEpConfigMd, + lz::EpConfig::ERROR::sameMsglib + ); +} + +(int, slice) setEpConfigOApp::revert::invalidExpiry(cell $storage) impure { + cell $epConfig = lz::EpConfig::New( + true, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + TIMEOUT_RECEIVE_MSGLIB_ADDRESS, + 0 + ); + + return test::handler::shouldFail( + setEpConfigOApp, + $epConfig, + lz::EpConfig::ERROR::invalidTimeoutExpiry + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + .tpush([setEpConfigOApp::success::basic, "setEpConfigOApp::success::basic"]) + .tpush([setEpConfigOApp::success::SendMsglib, "setEpConfigOApp::success::SendMsglib"]) + .tpush([setEpConfigOApp::success::ReceiveMsglib, "setEpConfigOApp::success::ReceiveMsglib"]) + .tpush([setEpConfigOApp::success::TimeoutMsglib, "setEpConfigOApp::success::TimeoutMsglib"]) + .tpush([setEpConfigOApp::success::SendAndReceiveMsglib, "setEpConfigOApp::success::SendAndReceiveMsglib"]) + .tpush([setEpConfigOApp::success::SendAndTimeoutMsglib, "setEpConfigOApp::success::SendAndTimeoutMsglib"]) + .tpush([setEpConfigOApp::success::ReceiveAndTimeoutMsglib, "setEpConfigOApp::success::ReceiveAndTimeoutMsglib"]) + .tpush([setEpConfigOApp::revert::invalidConfigWithSameMsglib, "setEpConfigOApp::revert::invalidConfigWithSameMsglib"]) + .tpush([setEpConfigOApp::revert::invalidExpiry, "setEpConfigOApp::revert::invalidExpiry"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelInitialize.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelInitialize.fc new file mode 100644 index 00000000..7a20db0d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelInitialize.fc @@ -0,0 +1,95 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/MdObj.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::mgmt"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(false)); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +;; ---------------------- initialize channel ---------------------------- + +(int, slice) initialize::revert::invalidSrcEid(cell $storage) impure { + cell $invalidPath = MOCK_SEND_PATH().cl::set(lz::Path::srcEid, 0); + setContractStorage($storage.cl::set(Channel::path, $invalidPath)); + + return test::handler::shouldFail(initialize, cl::nullObject(), Channel::ERROR::wrongPath); +} + +(int, slice) initialize::revert::invalidDstEid(cell $storage) impure { + cell $invalidPath = MOCK_SEND_PATH().cl::set(lz::Path::dstEid, 0); + setContractStorage($storage.cl::set(Channel::path, $invalidPath)); + + return test::handler::shouldFail(initialize, cl::nullObject(), Channel::ERROR::wrongPath); +} + +(int, slice) initialize::revert::invalidSrcOApp(cell $storage) impure { + cell $invalidPath = MOCK_SEND_PATH().cl::set(lz::Path::srcOApp, NULLADDRESS); + setContractStorage($storage.cl::set(Channel::path, $invalidPath)); + + return test::handler::shouldFail(initialize, cl::nullObject(), Channel::ERROR::wrongPath); +} + +(int, slice) initialize::revert::invalidDstOApp(cell $storage) impure { + cell $invalidPath = MOCK_SEND_PATH().cl::set(lz::Path::dstOApp, NULLADDRESS); + setContractStorage($storage.cl::set(Channel::path, $invalidPath)); + + return test::handler::shouldFail(initialize, cl::nullObject(), Channel::ERROR::wrongPath); +} + + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- initialize + .tpush([initialize::revert::invalidSrcEid, "initialize::revert::invalidSrcEid"]) + .tpush([initialize::revert::invalidDstEid, "initialize::revert::invalidDstEid"]) + .tpush([initialize::revert::invalidSrcOApp, "initialize::revert::invalidSrcOApp"]) + .tpush([initialize::revert::invalidDstOApp, "initialize::revert::invalidDstOApp"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelMsglibIntegration.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelMsglibIntegration.fc new file mode 100644 index 00000000..cb8b0eb5 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelMsglibIntegration.fc @@ -0,0 +1,215 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/MdObj.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::msglib"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(false)); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) syncMsglibConnection::success::initial(cell $storage) impure { + cell $mdAddress = md::MdAddress::New( + cl::nullObject(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + return test::handler::shouldPass( + syncMsglibConnection, + $mdAddress, + unsafeTuple([ + 0, + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE, + md::MdObj::New( + md::ChannelNonceInfo::New(1, 1), ;; first unexecuted and committed are == 1 + getInitialStorage() + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) syncMsglibConnection::success::nonce1Committed(cell $storage) impure { + commitNonce(1); + cell $mdAddress = md::MdAddress::New( + cl::nullObject(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + return test::handler::shouldPass( + syncMsglibConnection, + $mdAddress, + unsafeTuple([ + 0, + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE, + md::MdObj::New( + md::ChannelNonceInfo::New(2, 1), + getInitialStorage() + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) syncMsglibConnection::success::nonce1Executed(cell $storage) impure { + commitNonce(1); + lockNonce(1); + executeNonce(1); + cell $mdAddress = md::MdAddress::New( + cl::nullObject(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + return test::handler::shouldPass( + syncMsglibConnection, + $mdAddress, + unsafeTuple([ + 0, + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE, + md::MdObj::New( + md::ChannelNonceInfo::New(2, 2), + getInitialStorage() + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) notifyPacketExecuted::success::basic(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + executeNonce(incomingNonce); + $storage = getContractStorage(); + + cell $nonceMd = md::Nonce::New(incomingNonce); + return test::handler::shouldPass( + notifyPacketExecuted, + md::MdAddress::New($nonceMd, RECEIVE_MSGLIB_CONNECTION_ADDRESS), + unsafeTuple([ + 0, + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + incomingNonce, + $storage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) notifyPacketExecuted::success::executing(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + + cell $nonceMd = md::Nonce::New(incomingNonce); + return test::handler::shouldPass( + notifyPacketExecuted, + md::MdAddress::New($nonceMd, RECEIVE_MSGLIB_CONNECTION_ADDRESS), + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) notifyPacketExecuted::success::committed(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + + cell $nonceMd = md::Nonce::New(incomingNonce); + return test::handler::shouldPass( + notifyPacketExecuted, + md::MdAddress::New($nonceMd, RECEIVE_MSGLIB_CONNECTION_ADDRESS), + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) notifyPacketExecuted::success::uncommitted(cell $storage) impure { + int incomingNonce = 1; + + cell $nonceMd = md::Nonce::New(incomingNonce); + return test::handler::shouldPass( + notifyPacketExecuted, + md::MdAddress::New($nonceMd, RECEIVE_MSGLIB_CONNECTION_ADDRESS), + emptyActions(), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- initialize + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- syncMsglibConnection + .tpush([syncMsglibConnection::success::initial, "syncMsglibConnection::success::initial"]) + .tpush([syncMsglibConnection::success::nonce1Executed, "syncMsglibConnection::success::nonce1Executed"]) + .tpush([syncMsglibConnection::success::nonce1Committed, "syncMsglibConnection::success::nonce1Committed"]) + ;; -- notifyPacketExecuted + .tpush([notifyPacketExecuted::success::basic, "notifyPacketExecuted::success::basic"]) + .tpush([notifyPacketExecuted::success::executing, "notifyPacketExecuted::success::executing"]) + .tpush([notifyPacketExecuted::success::committed, "notifyPacketExecuted::success::committed"]) + .tpush([notifyPacketExecuted::success::uncommitted, "notifyPacketExecuted::success::uncommitted"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelMsglibSendCallback.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelMsglibSendCallback.fc new file mode 100644 index 00000000..32407c63 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelMsglibSendCallback.fc @@ -0,0 +1,492 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/BytesEncoder.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/MdObj.fc"; +#include "../../../classes/msgdata/LzSend.fc"; +#include "../../../classes/msgdata/MsglibSendCallback.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::send"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; --- helpers + +;; $mdMsglibSendCallback, expectedStorage, expectedLzSend, packet +(cell, cell, cell, cell) msglibSendCallback::prepareWorkerQuoteFail(cell $storage) impure { + int sendRequestId = getContractStorage().cl::get(Channel::lastSendRequestId) + 1; + + ;; make sure the channel has enough ZRO to pay the fee + setContractStorage($storage.cl::set(Channel::zroBalance, ZRO_FEE)); + + ;; create a send request and call channelSend + channelSend(md::MdObj::New(MOCK_LZ_SEND(), MOCK_SML_SEND_EP_CONFIG())); + + cell $mdMsglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND( + MOCK_LZ_SEND_WITH_ID(sendRequestId) + ); + + cell expectedStorage = getContractStorage() + .cl::set( + Channel::sendRequestQueue, + DeterministicInsertionCircularQueue::delete( + getContractStorage().cl::get(Channel::sendRequestQueue), + sendRequestId + ) + ); + + ;; create a mock send packet with nonce + cell $packet = MOCK_SEND_PACKET_WITH_NONCE(0); + + cell $expectedLzSend = MOCK_LZ_SEND_WITH_ID(sendRequestId) + .cl::set(md::LzSend::packet, $packet) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + return ($mdMsglibSendCallback, expectedStorage, $expectedLzSend, $packet); +} + +;; mdMsglibSendCallback, expectedStorage, expectedLzSend, packet, expectedEncodedPacket, packetNonce +(cell, cell, cell, cell, cell, int) msglibSendCallback::prepareSuccess(cell $storage, tuple payees) impure { + setContractStorage($storage.cl::set(Channel::zroBalance, ZRO_FEE)); + + int sendRequestId = getContractStorage() + .cl::get(Channel::lastSendRequestId) + 1; + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New( + $lzSend, + MOCK_SML_SEND_EP_CONFIG() + ); + channelSend($mdObj); + $storage = getContractStorage(); + + cell $packet = MOCK_SEND_PACKET(); + + int packetGuid = lz::Packet::calculateGuid(MOCK_SEND_PATH(), sendRequestId); + + cell $mdMsglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_PAYEES( + MOCK_LZ_SEND_WITH_ID(sendRequestId), + payees + ); + + int packetNonce = getContractStorage().cl::get(Channel::outboundNonce) + 1; + cell expectedEncodedPacket = BytesEncoder::build( + $packet + .cl::set(lz::Packet::nonce, packetNonce) + .cl::set(lz::Packet::guid, packetGuid) + ) + .BytesEncoder::serialize(); + + cell $expectedLzSend = MOCK_LZ_SEND_WITH_ID(sendRequestId) + .cl::set( + md::LzSend::packet, + $packet + .cl::set(lz::Packet::nonce, packetNonce) + .cl::set(lz::Packet::guid, packetGuid) + ) + .cl::set(md::LzSend::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell expectedSendQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::sendRequestQueue), + sendRequestId + ); + + cell expectedStorage = getContractStorage() + .cl::set(Channel::zroBalance, 0) + .cl::set(Channel::sendRequestQueue, expectedSendQueue) + .cl::set(Channel::outboundNonce, packetNonce); + + return ($mdMsglibSendCallback, expectedStorage, $expectedLzSend, $packet, expectedEncodedPacket, packetNonce); +} + +tuple generateActionsForMsglibSendCallback(cell $mdMsglibSendCallback, cell $expectedLzSend, cell $packet, cell expectedEncodedPacket, int packetNonce) impure { + (_, tuple actions) = preamble(); + cell serializedPayees = $mdMsglibSendCallback + .cl::get(md::MsglibSendCallback::payees); + tuple payees = deserializePayees(serializedPayees); + + int numPayees = payees.tlen(); + repeat (numPayees) { + [int payeeAddress, int nativeAmount] = payees~tpopPayee(); + actions~pushAction( + payeeAddress, + nativeAmount, + 0 + ); + } + + actions~pushAction( + Channel::event::PACKET_SENT, + ;; if state == success, emit the Packet sent event to the controller + md::PacketSent::New( + NATIVE_FEE, + ZRO_FEE, + $expectedLzSend.cl::get(md::LzSend::extraOptions), + $expectedLzSend.cl::get(md::LzSend::enforcedOptions), + expectedEncodedPacket, + packetNonce, + SEND_MSGLIB_MANAGER_ADDRESS, + lz::SmlJobAssigned::New(MOCK_FEE) + ;; that the msglib connection claims is the manager address + ) + ); + + actions~pushAction( + $packet + .cl::get(lz::Packet::path) + .cl::get
(lz::Path::srcOApp), + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + $expectedLzSend, + NATIVE_FEE, + ZRO_FEE, + Channel::NO_ERROR + ), + getInitialStorage() + ) + ); + + return actions; +} + +tuple generateActionsForMsglibSendCallbackWithErrors(cell $expectedLzSend, cell $packet) impure { + (_, tuple actions) = preamble(); + + actions~pushAction( + $packet + .cl::get(lz::Packet::path) + .cl::get
(lz::Path::srcOApp), + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + $expectedLzSend, + 0, + 0, + Uln::ErrorCode::WORKER_QUOTE_FAILED + ), + getInitialStorage() + ) + ); + + return actions; +} + +(int, slice) _msglibSendCallbackWithPayees(cell $storage, int numPayees) impure { + ( cell $mdMsglibSendCallback, + cell $expectedStorage, + cell $expectedLzSend, + cell $packet, + cell expectedEncodedPacket, + int packetNonce + ) = msglibSendCallback::prepareSuccess($storage, MOCK_PAYEES(numPayees)); + + return test::handler::shouldPass( + msglibSendCallback, + $mdMsglibSendCallback, + generateActionsForMsglibSendCallback( + $mdMsglibSendCallback, + $expectedLzSend, + $packet, + expectedEncodedPacket, + packetNonce + ), + $expectedStorage, + txnContext + ); +} + +;;; ===============================TESTS========================================= + +(int, slice) msglibSendCallback::success::zeroPayees(cell $storage) impure { + return _msglibSendCallbackWithPayees($storage, 0); +} + +(int, slice) msglibSendCallback::success::onePayee(cell $storage) impure { + return _msglibSendCallbackWithPayees($storage, 1); +} + +(int, slice) msglibSendCallback::success::twoPayees(cell $storage) impure { + return _msglibSendCallbackWithPayees($storage, 2); +} + +(int, slice) msglibSendCallback::success::threePayees(cell $storage) impure { + return _msglibSendCallbackWithPayees($storage, 3); +} + +(int, slice) msglibSendCallback::success::fourPayees(cell $storage) impure { + return _msglibSendCallbackWithPayees($storage, 4); +} + +(int, slice) msglibSendCallback::success::fivePayees(cell $storage) impure { + return _msglibSendCallbackWithPayees($storage, 5); +} + +(int, slice) msglibSendCallback::success::sixPayees(cell $storage) impure { + return _msglibSendCallbackWithPayees($storage, 6); +} + +(int, slice) msglibSendCallback::revert::notEnoughNative(cell $storage) impure { + setContractStorage($storage.cl::set(Channel::zroBalance, ZRO_FEE)); + int sendRequestId = getContractStorage().cl::get(Channel::lastSendRequestId) + 1; + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New( + $lzSend, + MOCK_SML_SEND_EP_CONFIG() + ); + channelSend($mdObj); + $storage = getContractStorage(); + + cell $mdMsglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_FEES( + MOCK_LZ_SEND_WITH_ID(sendRequestId), + NATIVE_FEE + 1, + ZRO_FEE + ); + + cell expectedSendQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::sendRequestQueue), + sendRequestId + ); + + return test::handler::shouldPass( + msglibSendCallback, + $mdMsglibSendCallback, + unsafeTuple([ + 0, + _newAction( + SRC_OAPP, + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + MOCK_LZ_SEND_WITH_ID(sendRequestId), + NATIVE_FEE + 1, + ZRO_FEE, + Channel::ERROR::notEnoughNative + ), + getInitialStorage() + ) + ) + ]), + $storage.cl::set(Channel::sendRequestQueue, expectedSendQueue), + txnContext + ); +} + +(int, slice) msglibSendCallback::revert::notEnoughZroToken(cell $storage) impure { + setContractStorage($storage.cl::set(Channel::zroBalance, ZRO_FEE + 1)); + int sendRequestId = getContractStorage().cl::get(Channel::lastSendRequestId) + 1; + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New( + $lzSend, + MOCK_SML_SEND_EP_CONFIG() + ); + channelSend($mdObj); + $storage = getContractStorage(); + + cell $mdMsglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_FEES( + MOCK_LZ_SEND_WITH_ID(sendRequestId), + NATIVE_FEE, + ZRO_FEE + 1 + ); + + cell expectedSendQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::sendRequestQueue), + sendRequestId + ); + + return test::handler::shouldPass( + msglibSendCallback, + $mdMsglibSendCallback, + unsafeTuple([ + 0, + _newAction( + SRC_OAPP, + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + MOCK_LZ_SEND_WITH_ID(sendRequestId), + NATIVE_FEE, + ZRO_FEE + 1, + Channel::ERROR::notEnoughZroToken + ), + getInitialStorage() + ) + ) + ]), + $storage.cl::set(Channel::sendRequestQueue, expectedSendQueue), + txnContext + ); +} + +(int, slice) msglibSendCallback::revert::notEnoughZroTokenBalance(cell $storage) impure { + setContractStorage($storage.cl::set(Channel::zroBalance, ZRO_FEE - 1)); + int sendRequestId = getContractStorage().cl::get(Channel::lastSendRequestId) + 1; + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New( + $lzSend, + MOCK_SML_SEND_EP_CONFIG() + ); + channelSend($mdObj); + $storage = getContractStorage(); + + cell $mdMsglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_FEES( + MOCK_LZ_SEND_WITH_ID(sendRequestId), + NATIVE_FEE, + ZRO_FEE + ); + + cell expectedSendQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::sendRequestQueue), + sendRequestId + ); + + return test::handler::shouldPass( + msglibSendCallback, + $mdMsglibSendCallback, + unsafeTuple([ + 0, + _newAction( + SRC_OAPP, + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + MOCK_LZ_SEND_WITH_ID(sendRequestId), + NATIVE_FEE, + ZRO_FEE, + Channel::ERROR::notEnoughZroTokenBalance + ), + getInitialStorage() + ) + ) + ]), + $storage.cl::set(Channel::sendRequestQueue, expectedSendQueue), + txnContext + ); +} + +(int, slice) msglibSendCallback::success::lzSendHashMismatch(cell $storage) impure { + channelSend(md::MdObj::New(MOCK_LZ_SEND_WITH_ID(9), MOCK_SML_SEND_EP_CONFIG())); + + cell $mdMsglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK() + .cl::set(md::MsglibSendCallback::lzSend, MOCK_LZ_SEND_WITH_ID(10)); + + return test::handler::shouldPass( + msglibSendCallback, + $mdMsglibSendCallback, + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) msglibSendCallback::success::lzSendHashMismatchWithCorrectRequestId(cell $storage) impure { + channelSend(md::MdObj::New(MOCK_LZ_SEND(), MOCK_SML_SEND_EP_CONFIG())); + + cell $mdMsglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK() + .cl::set( + md::MsglibSendCallback::lzSend, + MOCK_LZ_SEND_WITH_ID(1).cl::set(md::LzSend::nativeFee, NATIVE_FEE * 2) + ); + + return test::handler::shouldPass( + msglibSendCallback, + $mdMsglibSendCallback, + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) msglibSendCallback::success::mdMsglibSendCallbackWithError(cell $storage) impure { + ( cell $mdMsglibSendCallback, + cell $expectedStorage, + cell $expectedLzSend, + cell $packet + ) = msglibSendCallback::prepareWorkerQuoteFail($storage); + + return test::handler::shouldPass( + msglibSendCallback, + $mdMsglibSendCallback, + generateActionsForMsglibSendCallbackWithErrors( + $expectedLzSend, + $packet + ), + $expectedStorage, + txnContext + ); +} + +(int, slice) msglibSendCallback::success::sendRequestDoesNotExist(cell $storage) impure { + cell $mdMsglibSendCallback = MOCK_MSGLIB_SEND_CALLBACK() + .cl::set(md::MsglibSendCallback::lzSend, MOCK_LZ_SEND_WITH_ID(10)); + + return test::handler::shouldPass( + msglibSendCallback, + $mdMsglibSendCallback, + emptyActions(), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- msglib send Callback + .tpush([msglibSendCallback::success::zeroPayees, "msglibSendCallback::success::zeroPayees"]) + .tpush([msglibSendCallback::success::onePayee, "msglibSendCallback::success::onePayee"]) + .tpush([msglibSendCallback::success::twoPayees, "msglibSendCallback::success::twoPayees"]) + .tpush([msglibSendCallback::success::threePayees, "msglibSendCallback::success::threePayees"]) + .tpush([msglibSendCallback::success::fourPayees, "msglibSendCallback::success::fourPayees"]) + .tpush([msglibSendCallback::success::fivePayees, "msglibSendCallback::success::fivePayees"]) + .tpush([msglibSendCallback::success::sixPayees, "msglibSendCallback::success::sixPayees"]) + .tpush([msglibSendCallback::revert::notEnoughNative, "msglibSendCallback::revert::notEnoughNative"]) + .tpush([msglibSendCallback::revert::notEnoughZroToken, "msglibSendCallback::revert::notEnoughZroToken"]) + .tpush([msglibSendCallback::revert::notEnoughZroTokenBalance, "msglibSendCallback::revert::notEnoughZroTokenBalance"]) + .tpush([msglibSendCallback::success::lzSendHashMismatch, "msglibSendCallback::success::lzSendHashMismatch"]) + .tpush([msglibSendCallback::success::mdMsglibSendCallbackWithError, "msglibSendCallback::success::mdMsglibSendCallbackWithError"]) + .tpush([msglibSendCallback::success::sendRequestDoesNotExist, "msglibSendCallback::success::sendRequestDoesNotExist"]) + .tpush([msglibSendCallback::success::lzSendHashMismatchWithCorrectRequestId, "msglibSendCallback::success::lzSendHashMismatchWithCorrectRequestId"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelNilify.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelNilify.fc new file mode 100644 index 00000000..f376ea66 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelNilify.fc @@ -0,0 +1,439 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/msgdata/ChannelNonceInfo.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/LzReceivePrepare.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/MdObj.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::nilify"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(false)); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +;; ---------------------- nilify ---------------------------- + +(int, slice) nilify::success::uncommittedNonce(cell $storage) impure { + int incomingNonce = NONCE; + + cell $packetId = md::PacketId::New( + MOCK_RECEIVE_PATH(), + incomingNonce + ); + + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO).POOO::set(incomingNonce); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + incomingNonce + ); + + return test::handler::shouldPass( + nilify, + $packetId, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + lz::Packet::New( + MOCK_RECEIVE_PATH(), + empty_cell(), + incomingNonce + ) + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New( + $packetId, + getInitialStorage() + ) + ) + ]), + $storage + .cl::set(Channel::commitPOOO, $commitPOOO) + .cl::set(Channel::executionQueue, executionQueue), + txnContext + ); +} + +(int, slice) nilify::success::committedNonce(cell $storage) impure { + int incomingNonce = 1; + + commitNonce(incomingNonce); + + cell $packetId = md::PacketId::New( + MOCK_RECEIVE_PATH(), + incomingNonce + ); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + getContractStorage().cl::get(Channel::executionQueue), + incomingNonce + ); + + return test::handler::shouldPass( + nilify, + $packetId, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + lz::Packet::New( + MOCK_RECEIVE_PATH(), + MOCK_MESSAGE(), + incomingNonce + ) + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New($packetId, getInitialStorage()) + ) + ]), + getContractStorage().cl::set(Channel::executionQueue, executionQueue), + txnContext + ); +} + +(int, slice) nilify::success::idempotent(cell $storage) impure { + int incomingNonce = NONCE; + + cell $packetId = md::PacketId::New( + MOCK_RECEIVE_PATH(), + incomingNonce + ); + + cell $packetToNilify = MOCK_RECEIVE_PACKET_WITH_NONCE(incomingNonce) + .cl::set(lz::Packet::message, empty_cell()); + + + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO).POOO::set(incomingNonce); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + incomingNonce + ); + + + tuple expectedActions = unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + $packetToNilify + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New($packetId, getInitialStorage()) + ) + ]); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, $commitPOOO) + .cl::set(Channel::executionQueue, executionQueue); + + (int result1, slice reason1) = test::handler::shouldPass( + nilify, + $packetId, + expectedActions, + $expectedStorage, + txnContext + ); + (int result2, slice reason2) = test::handler::shouldPass( + nilify, + $packetId, + expectedActions, + $expectedStorage, + txnContext + ); + + if ((result1 == TEST_SUCCESS) & (result1 == result2) & equal_slice_bits(reason1, reason2)) { + return (TEST_SUCCESS, ""); + } else { + return (TEST_FAILED, "nilify is not idempotent"); + } +} + +(int, slice) nilify::success::recommit(cell $storage) impure { + int firstUncommittedNonce = 1; + + cell $packetId = md::PacketId::New( + MOCK_RECEIVE_PATH(), + firstUncommittedNonce + ); + + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO).POOO::set(firstUncommittedNonce); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + firstUncommittedNonce + ); + + tuple expectedEvents = unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + MOCK_RECEIVE_PACKET_WITH_NONCE(firstUncommittedNonce) + .cl::set(lz::Packet::message, empty_cell()) + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New($packetId, getInitialStorage()) + ) + ]); + + cell $nilifyExpectedStorage = $storage + .cl::set(Channel::commitPOOO, $commitPOOO) + .cl::set(Channel::executionQueue, executionQueue); + + (int nilifyResult, slice nilifyReason) = test::handler::shouldPass( + nilify, + $packetId, + expectedEvents, + $nilifyExpectedStorage, + txnContext + ); + + if ((nilifyResult != TEST_SUCCESS)) { + return (nilifyResult, nilifyReason); + } + + ;; commit the first uncommitted nonce + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(true)); + $storage = getContractStorage(); + + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(firstUncommittedNonce); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + firstUncommittedNonce, + $packet, + ExecutionQueue::committed + ); + + cell $commitExpectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, firstUncommittedNonce)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + firstUncommittedNonce, + $commitExpectedStorage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $commitExpectedStorage, + txnContext + ); +} + +(int, slice) nilify::success::unblockChannel(cell $storage) impure { + int nilifiedNonce = 1; + int committedNonce = 2; + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(committedNonce); + commitNonce(committedNonce); + $storage = getContractStorage(); + + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), nilifiedNonce); + + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO).POOO::set(nilifiedNonce); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + nilifiedNonce + ); + + tuple expectedActions = unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + lz::Packet::New( + MOCK_RECEIVE_PATH(), + empty_cell(), + nilifiedNonce + ) + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New($packetId, getInitialStorage()) + ) + ]); + + cell $nilifyExpectedStorage = $storage + .cl::set(Channel::commitPOOO, $commitPOOO) + .cl::set(Channel::executionQueue, executionQueue); + + (int nilifyResult, slice nilifyReason) = test::handler::shouldPass( + nilify, + $packetId, + expectedActions, + $nilifyExpectedStorage, + txnContext + ); + + if ((nilifyResult != TEST_SUCCESS)) { + return (nilifyResult, nilifyReason); + } + + return test::handler::shouldPass( + lzReceiveLock, + md::Nonce::New(committedNonce), + unsafeTuple([ + 0, + _newAction( + $storage + .cl::get(Channel::path) + .cl::get
(lz::Path::srcOApp), + Layerzero::OP::LZ_RECEIVE_EXECUTE, + md::MdObj::New( + $packet, + getInitialStorage() + ) + ) + ]), + getContractStorage().cl::set( + Channel::executionQueue, + DeterministicInsertionCircularQueue::set( + getContractStorage().cl::get(Channel::executionQueue), + committedNonce, + $packet, + ExecutionQueue::executing + ) + ), + txnContext + ); +} + +(int, slice) nilify::revert::nonceAlreadyExecuted(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + executeNonce(incomingNonce); + + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), incomingNonce); + + return test::handler::shouldFail( + nilify, + $packetId, + Channel::ERROR::notCommittable + ); +} + +(int, slice) nilify::revert::nonceExecuting(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), incomingNonce); + + return test::handler::shouldFail( + nilify, + $packetId, + Channel::ERROR::notCommittable + ); +} + +(int, slice) nilify::revert::nonceTooBig(cell $storage) impure { + ;; try to nilify a nonce beyond the capacity of the queue + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), MAX_CELL_BITS + 1); + + return test::handler::shouldFail( + nilify, + $packetId, + Channel::ERROR::notCommittable + ); +} + +(int, slice) nilify::revert::invalidNonce(cell $storage) impure { + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), 0); + + return test::handler::shouldFail( + nilify, + $packetId, + Channel::ERROR::invalidNonce + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- nilify + .tpush([nilify::success::uncommittedNonce, "nilify::success::uncommittedNonce"]) + .tpush([nilify::success::committedNonce, "nilify::success::committedNonce"]) + .tpush([nilify::success::idempotent, "nilify::success::idempotent"]) + .tpush([nilify::success::recommit, "nilify::success::recommit"]) + .tpush([nilify::success::unblockChannel, "nilify::success::unblockChannel"]) + .tpush([nilify::revert::nonceAlreadyExecuted, "nilify::revert::nonceAlreadyExecuted"]) + .tpush([nilify::revert::nonceExecuting, "nilify::revert::nonceExecuting"]) + .tpush([nilify::revert::nonceTooBig, "nilify::revert::nonceTooBig"]) + .tpush([nilify::revert::invalidNonce, "nilify::revert::invalidNonce"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelNilifyDefaultConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelNilifyDefaultConfig.fc new file mode 100644 index 00000000..dc375542 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelNilifyDefaultConfig.fc @@ -0,0 +1,438 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/msgdata/ChannelNonceInfo.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/LzReceivePrepare.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/MdObj.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::nilify::2"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +;; ---------------------- nilify ---------------------------- + +(int, slice) nilify::success::uncommittedNonce(cell $storage) impure { + int incomingNonce = NONCE; + + cell $packetId = md::PacketId::New( + MOCK_RECEIVE_PATH(), + incomingNonce + ); + + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO).POOO::set(incomingNonce); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + incomingNonce + ); + + return test::handler::shouldPass( + nilify, + $packetId, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + lz::Packet::New( + MOCK_RECEIVE_PATH(), + empty_cell(), + incomingNonce + ) + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New( + $packetId, + getInitialStorage() + ) + ) + ]), + $storage + .cl::set(Channel::commitPOOO, $commitPOOO) + .cl::set(Channel::executionQueue, executionQueue), + txnContext + ); +} + +(int, slice) nilify::success::committedNonce(cell $storage) impure { + int incomingNonce = 1; + + commitNonce(incomingNonce); + + cell $packetId = md::PacketId::New( + MOCK_RECEIVE_PATH(), + incomingNonce + ); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + getContractStorage().cl::get(Channel::executionQueue), + incomingNonce + ); + + return test::handler::shouldPass( + nilify, + $packetId, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + lz::Packet::New( + MOCK_RECEIVE_PATH(), + MOCK_MESSAGE(), + incomingNonce + ) + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New($packetId, getInitialStorage()) + ) + ]), + getContractStorage().cl::set(Channel::executionQueue, executionQueue), + txnContext + ); +} + +(int, slice) nilify::success::idempotent(cell $storage) impure { + int incomingNonce = NONCE; + + cell $packetId = md::PacketId::New( + MOCK_RECEIVE_PATH(), + incomingNonce + ); + + cell $packetToNilify = MOCK_RECEIVE_PACKET_WITH_NONCE(incomingNonce) + .cl::set(lz::Packet::message, empty_cell()); + + + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO).POOO::set(incomingNonce); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + incomingNonce + ); + + + tuple expectedActions = unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + $packetToNilify + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New($packetId, getInitialStorage()) + ) + ]); + + cell $expectedStorage = $storage + .cl::set(Channel::commitPOOO, $commitPOOO) + .cl::set(Channel::executionQueue, executionQueue); + + (int result1, slice reason1) = test::handler::shouldPass( + nilify, + $packetId, + expectedActions, + $expectedStorage, + txnContext + ); + (int result2, slice reason2) = test::handler::shouldPass( + nilify, + $packetId, + expectedActions, + $expectedStorage, + txnContext + ); + + if ((result1 == TEST_SUCCESS) & (result1 == result2) & equal_slice_bits(reason1, reason2)) { + return (TEST_SUCCESS, ""); + } else { + return (TEST_FAILED, "nilify is not idempotent"); + } +} + +(int, slice) nilify::success::recommit(cell $storage) impure { + int firstUncommittedNonce = 1; + + cell $packetId = md::PacketId::New( + MOCK_RECEIVE_PATH(), + firstUncommittedNonce + ); + + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO).POOO::set(firstUncommittedNonce); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + firstUncommittedNonce + ); + + tuple expectedEvents = unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + MOCK_RECEIVE_PACKET_WITH_NONCE(firstUncommittedNonce) + .cl::set(lz::Packet::message, empty_cell()) + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New($packetId, getInitialStorage()) + ) + ]); + + cell $nilifyExpectedStorage = $storage + .cl::set(Channel::commitPOOO, $commitPOOO) + .cl::set(Channel::executionQueue, executionQueue); + + (int nilifyResult, slice nilifyReason) = test::handler::shouldPass( + nilify, + $packetId, + expectedEvents, + $nilifyExpectedStorage, + txnContext + ); + + if ((nilifyResult != TEST_SUCCESS)) { + return (nilifyResult, nilifyReason); + } + + ;; commit the first uncommitted nonce + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(true)); + $storage = getContractStorage(); + + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(firstUncommittedNonce); + + ;; Create mock input + cell $mdExtended = md::ExtendedMd::New( + $packet, + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; setup the expected state + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO); + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + firstUncommittedNonce, + $packet, + ExecutionQueue::committed + ); + + cell $commitExpectedStorage = $storage + .cl::set(Channel::commitPOOO, POOO::set($commitPOOO, firstUncommittedNonce)) + .cl::set(Channel::executionQueue, executionQueue) + ; + + return test::handler::shouldPass( + channelCommitPacket, + $mdExtended, + unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_COMMITTED, + $packet + ), + _newAction( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New( + firstUncommittedNonce, + $commitExpectedStorage.cl::get(Channel::executePOOO).cl::get(POOO::nextEmpty) + ) + ) + ]), + $commitExpectedStorage, + txnContext + ); +} + +(int, slice) nilify::success::unblockChannel(cell $storage) impure { + int nilifiedNonce = 1; + int committedNonce = 2; + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(committedNonce); + commitNonce(committedNonce); + $storage = getContractStorage(); + + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), nilifiedNonce); + + cell $commitPOOO = $storage.cl::get(Channel::commitPOOO).POOO::set(nilifiedNonce); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::executionQueue), + nilifiedNonce + ); + + tuple expectedActions = unsafeTuple([ + 0, + _newAction( + Channel::event::PACKET_NILIFIED, + lz::Packet::New( + MOCK_RECEIVE_PATH(), + empty_cell(), + nilifiedNonce + ) + ), + _newAction( + SRC_OAPP, + Layerzero::OP::NILIFY_CALLBACK, + md::MdObj::New($packetId, getInitialStorage()) + ) + ]); + + cell $nilifyExpectedStorage = $storage + .cl::set(Channel::commitPOOO, $commitPOOO) + .cl::set(Channel::executionQueue, executionQueue); + + (int nilifyResult, slice nilifyReason) = test::handler::shouldPass( + nilify, + $packetId, + expectedActions, + $nilifyExpectedStorage, + txnContext + ); + + if ((nilifyResult != TEST_SUCCESS)) { + return (nilifyResult, nilifyReason); + } + + return test::handler::shouldPass( + lzReceiveLock, + md::Nonce::New(committedNonce), + unsafeTuple([ + 0, + _newAction( + $storage + .cl::get(Channel::path) + .cl::get
(lz::Path::srcOApp), + Layerzero::OP::LZ_RECEIVE_EXECUTE, + md::MdObj::New( + $packet, + getInitialStorage() + ) + ) + ]), + getContractStorage().cl::set( + Channel::executionQueue, + DeterministicInsertionCircularQueue::set( + getContractStorage().cl::get(Channel::executionQueue), + committedNonce, + $packet, + ExecutionQueue::executing + ) + ), + txnContext + ); +} + +(int, slice) nilify::revert::nonceAlreadyExecuted(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + executeNonce(incomingNonce); + + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), incomingNonce); + + return test::handler::shouldFail( + nilify, + $packetId, + Channel::ERROR::notCommittable + ); +} + +(int, slice) nilify::revert::nonceExecuting(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), incomingNonce); + + return test::handler::shouldFail( + nilify, + $packetId, + Channel::ERROR::notCommittable + ); +} + +(int, slice) nilify::revert::nonceTooBig(cell $storage) impure { + ;; try to nilify a nonce beyond the capacity of the queue + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), MAX_CELL_BITS + 1); + + return test::handler::shouldFail( + nilify, + $packetId, + Channel::ERROR::notCommittable + ); +} + +(int, slice) nilify::revert::invalidNonce(cell $storage) impure { + cell $packetId = md::PacketId::New(MOCK_RECEIVE_PATH(), 0); + + return test::handler::shouldFail( + nilify, + $packetId, + Channel::ERROR::invalidNonce + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- nilify + .tpush([nilify::success::uncommittedNonce, "nilify::success::uncommittedNonce"]) + .tpush([nilify::success::committedNonce, "nilify::success::committedNonce"]) + .tpush([nilify::success::idempotent, "nilify::success::idempotent"]) + .tpush([nilify::success::recommit, "nilify::success::recommit"]) + .tpush([nilify::success::unblockChannel, "nilify::success::unblockChannel"]) + .tpush([nilify::revert::nonceAlreadyExecuted, "nilify::revert::nonceAlreadyExecuted"]) + .tpush([nilify::revert::nonceExecuting, "nilify::revert::nonceExecuting"]) + .tpush([nilify::revert::nonceTooBig, "nilify::revert::nonceTooBig"]) + .tpush([nilify::revert::invalidNonce, "nilify::revert::invalidNonce"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceive.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceive.fc new file mode 100644 index 00000000..7840a1ab --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceive.fc @@ -0,0 +1,418 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/LzReceivePrepare.fc"; +#include "../../../classes/msgdata/MdObj.fc"; +#include "../../../funC++/actions/dispatch.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::receive"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(false)); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +;; --------------- LZ Receive Prepare ----------------- + +(int, slice) lzReceivePrepare::success::basic(cell $storage) impure { + cell $commitShiftMap = $storage.cl::get(Channel::commitPOOO); + cell $executeShiftMap = $storage.cl::get(Channel::executePOOO); + + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + NONCE, + MOCK_SEND_PACKET(), + ExecutionQueue::committed + ); + + setContractStorage($storage + .cl::set( + Channel::executePOOO, + $executeShiftMap.cl::set(POOO::nextEmpty, NONCE) + ) + .cl::set( + Channel::commitPOOO, + $commitShiftMap.cl::set(POOO::nextEmpty, NONCE + 1) + ) + .cl::set( + Channel::executionQueue, + executionQueue + ) + ); + + cell $lzRecievePrepareMd = md::LzReceivePrepare::New(NONCE, LZ_RECEIVE_PREPARE_GAS); + + return test::handler::shouldPass( + lzReceivePrepare, + $lzRecievePrepareMd, + unsafeTuple([ + 0, + _newAction( + SRC_OAPP, + Layerzero::OP::LZ_RECEIVE_PREPARE, + MOCK_SEND_PACKET(), + LZ_RECEIVE_PREPARE_GAS + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; nonces below and equal to 0 are disallowed +(int, slice) lzReceivePrepare::revert::invalidNonce(cell $storage) impure { + return test::handler::shouldFail( + lzReceivePrepare, + md::LzReceivePrepare::New(0, LZ_RECEIVE_PREPARE_GAS), + Channel::ERROR::invalidNonce + ); +} + +;; eg channel just initialized, no nonce has been committed and every nonce is committable +(int, slice) lzReceivePrepare::revert::nullPacket(cell $storage) impure { + return test::handler::shouldFail( + lzReceivePrepare, + md::LzReceivePrepare::New(NONCE, LZ_RECEIVE_PREPARE_GAS), + Channel::ERROR::notExecutable + ); +} + +;; nonce is smaller than the first available index in the queue +(int, slice) lzReceivePrepare::revert::nonceTooSmall(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + executeNonce(incomingNonce); + + commitNonce(2); + lockNonce(2); + + return test::handler::shouldFail( + lzReceivePrepare, + md::LzReceivePrepare::New(incomingNonce, LZ_RECEIVE_PREPARE_GAS), + Channel::ERROR::notExecutable + ); +} + +;; nonce is bigger than the last available index in the queue +(int, slice) lzReceivePrepare::revert::nonceTooBig(cell $storage) impure { + commitNonce(1); + + int firstUnexecutedNonce = getContractStorage() + .cl::get(Channel::executePOOO) + .cl::get(POOO::nextEmpty); + + return test::handler::shouldFail( + lzReceivePrepare, + md::LzReceivePrepare::New(firstUnexecutedNonce + MAX_CELL_BITS + 1, LZ_RECEIVE_PREPARE_GAS), + Channel::ERROR::notExecutable + ); +} + +;; it is within window but the packet is already executed +(int, slice) lzReceivePrepare::revert::alreadyExecuted(cell $storage) impure { + commitNonce(1); + lockNonce(1); + + int incomingNonce = 2; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + executeNonce(incomingNonce); + + return test::handler::shouldFail( + lzReceivePrepare, + md::LzReceivePrepare::New(incomingNonce, LZ_RECEIVE_PREPARE_GAS), + Channel::ERROR::notExecutable + ); +} + +;; it is within window but the state is executing +(int, slice) lzReceivePrepare::revert::nonceExecuting(cell $storage) impure { + commitNonce(1); + lockNonce(1); + + int incomingNonce = 2; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + + return test::handler::shouldFail( + lzReceivePrepare, + md::LzReceivePrepare::New(incomingNonce, LZ_RECEIVE_PREPARE_GAS), + Channel::ERROR::notExecutable + ); +} + +;; --------------- LZ Receive Lock ----------------- + +(int, slice) lzReceiveLock::success::nonce1(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(incomingNonce); + + cell executionQueue = DeterministicInsertionCircularQueue::set( + getContractStorage().cl::get(Channel::executionQueue), + incomingNonce, + $packet, + ExecutionQueue::executing + ); + + cell $expectedStorage = getContractStorage() + .cl::set(Channel::executionQueue, executionQueue); + + return test::handler::shouldPass( + lzReceiveLock, + md::Nonce::New(incomingNonce), + unsafeTuple([ + 0, + _newAction( + $storage + .cl::get(Channel::path) + .cl::get
(lz::Path::srcOApp), + Layerzero::OP::LZ_RECEIVE_EXECUTE, + md::MdObj::New( + $packet, + getInitialStorage() + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) lzReceiveLock::success::nonce1Helper(cell $storage) impure { + int incomingNonce = 1; + cell $packet = MOCK_RECEIVE_PACKET_WITH_NONCE(incomingNonce); + + commitNonce(incomingNonce); + $storage = getContractStorage(); + + lockNonce(1); + cell $expectedStorage = getContractStorage(); + setContractStorage($storage); + + return test::handler::shouldPass( + lzReceiveLock, + md::Nonce::New(incomingNonce), + unsafeTuple([ + 0, + _newAction( + $storage + .cl::get(Channel::path) + .cl::get
(lz::Path::srcOApp), + Layerzero::OP::LZ_RECEIVE_EXECUTE, + md::MdObj::New( + $packet, + getInitialStorage() + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +;; the current nonce is uncommitted and therefore can't be locked. +(int, slice) lzReceiveLock::success::uncommitted(cell $storage) impure { + commitNonce(1); + commitNonce(2); + commitNonce(3); + int incomingNonce = 4; + + return test::handler::shouldPass( + lzReceiveLock, + md::Nonce::New(incomingNonce), + unsafeTuple([ + 0, + _newAction( + Channel::event::NOT_EXECUTABLE, + md::PacketId::New( + getContractStorage().cl::get(Channel::path).lz::Path::reverse(), + incomingNonce + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; some nonce before the current one is uncommitted so we can't lock this one +(int, slice) lzReceiveLock::success::previousUncommittedNonce(cell $storage) impure { + commitNonce(1); + ;; nonce 2 remains uncommitted + int incomingNonce = 3; + commitNonce(incomingNonce); + + return test::handler::shouldPass( + lzReceiveLock, + md::Nonce::New(incomingNonce), + unsafeTuple([ + 0, + _newAction( + Channel::event::NOT_EXECUTABLE, + md::PacketId::New( + getContractStorage().cl::get(Channel::path).lz::Path::reverse(), + incomingNonce + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) lzReceiveLock::success::ignoreAlreadyExecuted(cell $storage) impure { + commitNonce(1); + commitNonce(3); + + int incomingNonce = 2; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + executeNonce(incomingNonce); + + return test::handler::shouldPass( + lzReceiveLock, + md::Nonce::New(incomingNonce), + unsafeTuple([ + 0, + _newAction( + Channel::event::NOT_EXECUTABLE, + md::PacketId::New( + getContractStorage().cl::get(Channel::path).lz::Path::reverse(), + incomingNonce + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) lzReceiveLock::success::nonceTooBig(cell $storage) impure { + int lastExecutedNonce = 1; + commitNonce(lastExecutedNonce); + lockNonce(lastExecutedNonce); + executeNonce(lastExecutedNonce); + + int tooBigNonce = lastExecutedNonce + MAX_CELL_BITS + 1; + + return test::handler::shouldPass( + lzReceiveLock, + md::Nonce::New(tooBigNonce), + unsafeTuple([ + 0, + _newAction( + Channel::event::NOT_EXECUTABLE, + md::PacketId::New( + getContractStorage().cl::get(Channel::path).lz::Path::reverse(), + tooBigNonce + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) lzReceiveLock::success::nonceExecuting(cell $storage) impure { + commitNonce(1); + lockNonce(1); + + int incomingNonce = 2; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + + commitNonce(3); + lockNonce(3); + + return test::handler::shouldPass( + lzReceiveLock, + md::Nonce::New(incomingNonce), + unsafeTuple([ + 0, + _newAction( + Channel::event::NOT_EXECUTABLE, + md::PacketId::New( + getContractStorage().cl::get(Channel::path).lz::Path::reverse(), + incomingNonce + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) lzReceiveLock::revert::invalidNonce(cell $storage) impure { + return test::handler::shouldFail( + lzReceiveLock, + md::Nonce::New(0), + Channel::ERROR::invalidNonce + ); +} + +;; -------------------------------------------------- + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- lz receive prepare + .tpush([lzReceivePrepare::success::basic, "lzReceivePrepare::success::basic"]) + .tpush([lzReceivePrepare::revert::invalidNonce, "lzReceivePrepare::revert::invalidNonce"]) + .tpush([lzReceivePrepare::revert::nullPacket, "lzReceivePrepare::revert::nullPacket"]) + .tpush([lzReceivePrepare::revert::nonceTooSmall, "lzReceivePrepare::revert::nonceTooSmall"]) + .tpush([lzReceivePrepare::revert::nonceTooBig, "lzReceivePrepare::revert::nonceTooBig"]) + .tpush([lzReceivePrepare::revert::alreadyExecuted, "lzReceivePrepare::revert::alreadyExecuted"]) + .tpush([lzReceivePrepare::revert::nonceExecuting, "lzReceivePrepare::revert::nonceExecuting"]) + ;; -- lz receive lock + .tpush([lzReceiveLock::success::nonce1, "lzReceiveLock::success::nonce1"]) + .tpush([lzReceiveLock::success::nonce1Helper, "lzReceiveLock::success::nonce1Helper"]) + .tpush([lzReceiveLock::revert::invalidNonce, "lzReceiveLock::revert::invalidNonce"]) + .tpush([lzReceiveLock::success::uncommitted, "lzReceiveLock::success::uncommitted"]) + .tpush([lzReceiveLock::success::previousUncommittedNonce, "lzReceiveLock::success::previousUncommittedNonce"]) + .tpush([lzReceiveLock::success::nonceTooBig, "lzReceiveLock::success::nonceTooBig"]) + .tpush([lzReceiveLock::success::ignoreAlreadyExecuted, "lzReceiveLock::success::ignoreAlreadyExecuted"]) + .tpush([lzReceiveLock::success::nonceExecuting, "lzReceiveLock::success::nonceExecuting"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveCallback.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveCallback.fc new file mode 100644 index 00000000..51ce9364 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveCallback.fc @@ -0,0 +1,321 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/dataStructures/DeterministicInsertionCircularQueue.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/MdObj.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::receive"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); + setEpConfigOApp(MOCK_RESOLVED_EP_CONFIG(false)); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +;; --------------- LZ Receive Callback ----------------- + +(int, slice) lzReceiveExecuteCallback::success::nonce1(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + + cell $executePOOO = getContractStorage().cl::get(Channel::executePOOO); + + cell executionQueue = DeterministicInsertionCircularQueue::delete( + getContractStorage().cl::get(Channel::executionQueue), + incomingNonce + ); + + cell $expectedStorage = getContractStorage() + .cl::set(Channel::executePOOO, POOO::set($executePOOO, incomingNonce)) + .cl::set(Channel::executionQueue, executionQueue); + + return test::handler::shouldPass( + lzReceiveExecuteCallback, + md::LzReceiveStatus::New(true, incomingNonce), + unsafeTuple([ + 0, + _newAction( + Channel::event::DELIVERED, + md::PacketId::New( + getContractStorage().cl::get(Channel::path).lz::Path::reverse(), + incomingNonce + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) lzReceiveExecuteCallback::success::nonce1Helper(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + + $storage = getContractStorage(); + executeNonce(incomingNonce); + + ;; get the expected storage after calling the helper and + ;; revert the state to what it was before to call the shouldPass + cell $expectedStorage = getContractStorage(); + setContractStorage($storage); + + return test::handler::shouldPass( + lzReceiveExecuteCallback, + md::LzReceiveStatus::New(true, incomingNonce), + unsafeTuple([ + 0, + _newAction( + Channel::event::DELIVERED, + md::PacketId::New( + getContractStorage().cl::get(Channel::path).lz::Path::reverse(), + incomingNonce + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +;; will emit a packet with NOT_DELIVERED if it's executing and we send a +;; lzReceiveStatus with success=false +(int, slice) lzReceiveExecuteCallback::success::nonce1Unlock(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + + $storage = getContractStorage(); + + cell executionQueue = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::executionQueue), + incomingNonce, + MOCK_RECEIVE_PACKET_WITH_NONCE(incomingNonce), + ExecutionQueue::committed + ); + + cell $expectedStorage = $storage + .cl::set( + Channel::executionQueue, + executionQueue + ); + + return test::handler::shouldPass( + lzReceiveExecuteCallback, + md::LzReceiveStatus::New(false, incomingNonce), + unsafeTuple([ + 0, + _newAction( + Channel::event::LZ_RECEIVE_ALERT, + md::LzReceiveStatus::NewFull( + false, + incomingNonce, + 0, + empty_cell(), + empty_cell(), + getOrigin(), + MOCK_RECEIVE_PACKET_WITH_NONCE(incomingNonce), + ExecutionStatus::executable + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +;; will emit a packet with NOT_DELIVERED if it's executing and we send a +;; lzReceiveStatus with success=false +(int, slice) lzReceiveExecuteCallback::success::nonce1UnlockHelper(cell $storage) impure { + int incomingNonce = 1; + commitNonce(incomingNonce); + lockNonce(incomingNonce); + + $storage = getContractStorage(); + unlockNonce(incomingNonce); + + ;; get the expected storage after calling the helper and + ;; revert the state to what it was before to call the shouldPass + cell $expectedStorage = getContractStorage(); + setContractStorage($storage); + + return test::handler::shouldPass( + lzReceiveExecuteCallback, + md::LzReceiveStatus::New(false, incomingNonce), + unsafeTuple([ + 0, + _newAction( + Channel::event::LZ_RECEIVE_ALERT, + md::LzReceiveStatus::NewFull( + false, + incomingNonce, + 0, + empty_cell(), + empty_cell(), + getOrigin(), + MOCK_RECEIVE_PACKET_WITH_NONCE(incomingNonce), + ExecutionStatus::executable + ) + ) + ]), + $expectedStorage, + txnContext + ); +} + +;; nonce is not locked and therefore can't be executed +(int, slice) lzReceiveExecuteCallback::revert::notExecutingOnSuccess(cell $storage) impure { + commitNonce(1); + commitNonce(2); + int incomingNonce = 3; + commitNonce(incomingNonce); + + return test::handler::shouldFail( + lzReceiveExecuteCallback, + md::LzReceiveStatus::New(true, incomingNonce), + Channel::ERROR::notExecuting + ); +} + +;; nonce is not locked and therefore can't be executed +(int, slice) lzReceiveExecuteCallback::revert::notExecutingOnFailure(cell $storage) impure { + commitNonce(1); + commitNonce(2); + int incomingNonce = 3; + commitNonce(incomingNonce); + + return test::handler::shouldFail( + lzReceiveExecuteCallback, + md::LzReceiveStatus::New(false, incomingNonce), + Channel::ERROR::notExecuting + ); +} + +(int, slice) lzReceiveExecuteCallback::revert::nonceTooBigOnSuccess(cell $storage) impure { + commitNonce(1); + lockNonce(1); + + int lastLockedNonce = 2; + commitNonce(lastLockedNonce); + lockNonce(lastLockedNonce); + + return test::handler::shouldFail( + lzReceiveExecuteCallback, + md::LzReceiveStatus::New(true, lastLockedNonce + MAX_CELL_BITS), + Channel::ERROR::notExecuting + ); +} + +(int, slice) lzReceiveExecuteCallback::revert::nonceTooBigOnFailure(cell $storage) impure { + commitNonce(1); + lockNonce(1); + + int lastLockedNonce = 2; + commitNonce(lastLockedNonce); + lockNonce(lastLockedNonce); + + return test::handler::shouldFail( + lzReceiveExecuteCallback, + md::LzReceiveStatus::New(false, lastLockedNonce + MAX_CELL_BITS), + Channel::ERROR::notExecuting + ); +} + +(int, slice) emitPacketStatus::success::basic(cell $storage) impure { + int nonce = 1; + commitNonce(nonce); + + cell $lzReceiveStatus = md::LzReceiveStatus::NewFull( + false, + nonce, + EXECUTION_VALUE_NANOS, + MOCK_EXTRA_DATA(), + MOCK_REASON(), + ;; Next 3 fields are ignored in input + NULLADDRESS, + cl::nullObject(), + 0 + ); + + cell $expectedLzReceiveStatus = md::LzReceiveStatus::NewFull( + false, + nonce, + EXECUTION_VALUE_NANOS, + MOCK_EXTRA_DATA(), + MOCK_REASON(), + getCaller(), + MOCK_RECEIVE_PACKET_WITH_NONCE(nonce), + ExecutionStatus::executable + ); + + return test::handler::shouldPass( + emitLzReceiveAlert, + $lzReceiveStatus, + unsafeTuple([ + 0, + _newAction( + Channel::event::LZ_RECEIVE_ALERT, + $expectedLzReceiveStatus + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; -------------------------------------------------- + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- lz receive callback + .tpush([lzReceiveExecuteCallback::success::nonce1, "lzReceiveExecuteCallback::success::nonce1"]) + .tpush([lzReceiveExecuteCallback::success::nonce1Helper, "lzReceiveExecuteCallback::success::nonce1Helper"]) + .tpush([lzReceiveExecuteCallback::success::nonce1Unlock, "lzReceiveExecuteCallback::success::nonce1Unlock"]) + .tpush([lzReceiveExecuteCallback::success::nonce1UnlockHelper, "lzReceiveExecuteCallback::success::nonce1UnlockHelper"]) + .tpush([lzReceiveExecuteCallback::revert::notExecutingOnSuccess, "lzReceiveExecuteCallback::revert::notExecutingOnSuccess"]) + .tpush([lzReceiveExecuteCallback::revert::notExecutingOnFailure, "lzReceiveExecuteCallback::revert::notExecutingOnFailure"]) + .tpush([lzReceiveExecuteCallback::revert::nonceTooBigOnSuccess, "lzReceiveExecuteCallback::revert::nonceTooBigOnSuccess"]) + .tpush([lzReceiveExecuteCallback::revert::nonceTooBigOnFailure, "lzReceiveExecuteCallback::revert::nonceTooBigOnFailure"]) + ;; -- emitPacketStatus + .tpush([emitPacketStatus::success::basic, "emitPacketStatus::success::basic"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveUtils.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveUtils.fc new file mode 100644 index 00000000..b3cae930 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveUtils.fc @@ -0,0 +1,28 @@ +#include "../handler.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/LzReceiveStatus.fc"; +#include "../../../classes/msgdata/Nonce.fc"; + +() commitNonce(int nonce) impure inline { + channelCommitPacket( + md::ExtendedMd::New( + MOCK_RECEIVE_PACKET_WITH_NONCE(nonce), + MOCK_SML_RECEIVE_EP_CONFIG(), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ) + ); +} + +() lockNonce(int nonce) impure inline { + lzReceiveLock(md::Nonce::New(nonce)); +} + +() executeNonce(int nonce) impure inline { + lzReceiveExecuteCallback(md::LzReceiveStatus::New(true, nonce)); +} + +() unlockNonce(int nonce) impure inline { + lzReceiveExecuteCallback(md::LzReceiveStatus::New(false, nonce)); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveView.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveView.fc new file mode 100644 index 00000000..744d48aa --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelReceiveView.fc @@ -0,0 +1,121 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "channelReceiveUtils.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::receive::view"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) getExecutionStatus::nonce1Verifying(cell $storage) impure { + return test::shouldBeTrue( + _viewExecutionStatus(1) == ExecutionStatus::uncommitted + ); +} + +(int, slice) getExecutionStatus::nonceVerifying(cell $storage) impure { + return test::shouldBeTrue( + _viewExecutionStatus(NONCE) == ExecutionStatus::uncommitted + ); +} + +(int, slice) getExecutionStatus::nonce1executable(cell $storage) impure { + commitNonce(1); + return test::shouldBeTrue( + _viewExecutionStatus(1) == ExecutionStatus::executable + ); +} + +(int, slice) getExecutionStatus::nonce3executable(cell $storage) impure { + commitNonce(1); + commitNonce(2); + commitNonce(3); + return test::shouldBeTrue( + _viewExecutionStatus(3) == ExecutionStatus::executable + ); +} + +(int, slice) getExecutionStatus::nonce1executing(cell $storage) impure { + commitNonce(1); + lockNonce(1); + return test::shouldBeTrue( + _viewExecutionStatus(1) == ExecutionStatus::executing + ); +} + +(int, slice) getExecutionStatus::nonce3executing(cell $storage) impure { + commitNonce(1); + commitNonce(2); + lockNonce(2); + executeNonce(2); + commitNonce(3); + lockNonce(3); + return test::shouldBeTrue( + (_viewExecutionStatus(1) == ExecutionStatus::executable) + & (_viewExecutionStatus(2) == ExecutionStatus::executed) + & (_viewExecutionStatus(3) == ExecutionStatus::executing) + ); +} + +(int, slice) getExecutionStatus::nonce1executed(cell $storage) impure { + commitNonce(1); + lockNonce(1); + executeNonce(1); + return test::shouldBeTrue( + _viewExecutionStatus(1) == ExecutionStatus::executed + ); +} + +(int, slice) getExecutionStatus::nonceVerifiedButNotExecutable(cell $storage) impure { + commitNonce(NONCE); + return test::shouldBeTrue( + _viewExecutionStatus(NONCE) == ExecutionStatus::committedNotExecutable + ); +} + +(int, slice) getExecutionStatus::nonce1024Verifying(cell $storage) impure { + return test::shouldBeTrue( + _viewExecutionStatus(MAX_CELL_BITS + 1) == ExecutionStatus::uncommitted + ); +} + +;; -------------------------------------------------- + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([getExecutionStatus::nonce1Verifying, "getExecutionStatus::nonce1Verifying"]) + .tpush([getExecutionStatus::nonceVerifying, "getExecutionStatus::nonceVerifying"]) + .tpush([getExecutionStatus::nonce1executable, "getExecutionStatus::nonce1executable"]) + .tpush([getExecutionStatus::nonce3executable, "getExecutionStatus::nonce3executable"]) + .tpush([getExecutionStatus::nonce1executing, "getExecutionStatus::nonce1executing"]) + .tpush([getExecutionStatus::nonce3executing, "getExecutionStatus::nonce3executing"]) + .tpush([getExecutionStatus::nonce1executed, "getExecutionStatus::nonce1executed"]) + .tpush([getExecutionStatus::nonceVerifiedButNotExecutable, "getExecutionStatus::nonceVerifiedButNotExecutable"]) + .tpush([getExecutionStatus::nonce1024Verifying, "getExecutionStatus::nonce1024Verifying"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelSend.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelSend.fc new file mode 100644 index 00000000..b5aff66f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelSend.fc @@ -0,0 +1,508 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../msglibs/BytesEncoder.fc"; +#include "../../msglibs/interface.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/MdObj.fc"; +#include "../../../classes/msgdata/LzSend.fc"; +#include "../../../classes/msgdata/MsglibSendCallback.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::send"; } + +cell createContractStorage() impure { + setContractStorage( + Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +(int, slice) channelSend::success::basic(cell $storage) impure { + int nextSendRequestId = $storage.cl::get(Channel::lastSendRequestId) + 1; + + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New($lzSend, MOCK_SML_SEND_EP_CONFIG()); + + cell $expectedLzSendMd = MOCK_LZ_SEND_WITH_ID(nextSendRequestId) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell sendRequests = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::sendRequestQueue), + nextSendRequestId, + _buildSendRequestQueueEntry($expectedLzSendMd), + SendRequestQueue::sending + ); + + cell $expectedStorage = $storage + .cl::set(Channel::sendRequestQueue, sendRequests) + .cl::set(Channel::lastSendRequestId, nextSendRequestId); + + return test::handler::shouldPass( + channelSend, + $mdObj, + unsafeTuple([ + 0, + _newAction( + SEND_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_SEND, + $expectedLzSendMd + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelSend::success::sendQueueMax(cell $storage) impure { + ;; this request `hashes` to the same slot as 1 + + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New( + $lzSend, + MOCK_SML_SEND_EP_CONFIG() + ); + + channelSend($mdObj); + + int nextSendRequestId = MAX_CELL_BITS; + setContractStorage( + getContractStorage() + .cl::set(Channel::lastSendRequestId, nextSendRequestId - 1) + ); + $storage = getContractStorage(); + + $lzSend = MOCK_LZ_SEND(); + $mdObj = md::MdObj::New( + $lzSend, + MOCK_SML_SEND_EP_CONFIG() + ); + + cell $expectedLzSendMd = MOCK_LZ_SEND_WITH_ID(nextSendRequestId); + + cell sendRequests = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::sendRequestQueue), + nextSendRequestId, + _buildSendRequestQueueEntry($expectedLzSendMd), + SendRequestQueue::sending + ); + + cell $expectedStorage = $storage + .cl::set(Channel::sendRequestQueue, sendRequests) + .cl::set(Channel::lastSendRequestId, nextSendRequestId); + + return test::handler::shouldPass( + channelSend, + $mdObj, + unsafeTuple([ + 0, + _newAction( + SEND_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_SEND, + $expectedLzSendMd + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelSend::success::sendQueueCongested(cell $storage) impure { + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New( + $lzSend, + MOCK_SML_SEND_EP_CONFIG() + ); + + ;; to move the last send request id one step forward + channelSend($mdObj); + + setContractStorage( + getContractStorage() + .cl::set(Channel::lastSendRequestId, MAX_CELL_BITS) + ); + + $lzSend = MOCK_LZ_SEND_WITH_ID(MAX_CELL_BITS + 1) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + return test::handler::shouldPass( + channelSend, + $mdObj, + unsafeTuple([ + 0, + _newAction( + Channel::ERROR::sendQueueCongested, + $lzSend + ), + _newAction( + SRC_OAPP, + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + $lzSend, + 0, + 0, + Channel::ERROR::sendQueueCongested + ), + getInitialStorage() + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) channelSend::success::maxSizeMessage(cell $storage) impure { + int nextSendRequestId = $storage.cl::get(Channel::lastSendRequestId) + 1; + + cell tail = begin_cell().store_ones(MAX_CELL_WHOLE_BYTE_BITS).end_cell(); + repeat(lz::Packet::MAX_SEND_MESSAGE_CELLS - 1) { + tail = begin_cell() + .store_ones(MAX_CELL_WHOLE_BYTE_BITS) + .store_ref(tail) + .end_cell(); + } + cell $lzSend = MOCK_LZ_SEND() + .cl::set(md::LzSend::packet, MOCK_PACKET_WITH_MESSAGE(tail)); + cell $mdObj = md::MdObj::New($lzSend, MOCK_SML_SEND_EP_CONFIG()); + + cell $expectedLzSendMd = $lzSend + .cl::set(md::LzSend::sendRequestId, nextSendRequestId) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell sendRequests = DeterministicInsertionCircularQueue::set( + $storage.cl::get(Channel::sendRequestQueue), + nextSendRequestId, + _buildSendRequestQueueEntry($expectedLzSendMd), + SendRequestQueue::sending + ); + + cell $expectedStorage = $storage + .cl::set(Channel::sendRequestQueue, sendRequests) + .cl::set(Channel::lastSendRequestId, nextSendRequestId); + + return test::handler::shouldPass( + channelSend, + $mdObj, + unsafeTuple([ + 0, + _newAction( + SEND_MSGLIB_CONNECTION_ADDRESS, + MsglibConnection::OP::MSGLIB_CONNECTION_SEND, + $expectedLzSendMd + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) channelSend::revert::messageTooBig(cell $storage) impure { + cell tail = begin_cell().store_ones(MAX_CELL_WHOLE_BYTE_BITS).end_cell(); + repeat(lz::Packet::MAX_SEND_MESSAGE_CELLS) { + tail = begin_cell() + .store_ones(MAX_CELL_WHOLE_BYTE_BITS) + .store_ref(tail) + .end_cell(); + } + cell $lzSend = MOCK_LZ_SEND() + .cl::set(md::LzSend::packet, MOCK_PACKET_WITH_MESSAGE(tail)); + + return test::handler::shouldFail( + channelSend, + md::MdObj::New( + $lzSend, + MOCK_SML_SEND_EP_CONFIG() + ), + lz::Packet::ERROR::INVALID_MESSAGE + ); +} + +(int, slice) channelSend::revert::messageNotByteAligned(cell $storage) impure { + cell $lzSend = MOCK_LZ_SEND() + .cl::set( + md::LzSend::packet, + MOCK_PACKET_WITH_MESSAGE( + begin_cell() + .store_bool(true) + .end_cell() + ) + ); + + return test::handler::shouldFail( + channelSend, + md::MdObj::New($lzSend, MOCK_SML_SEND_EP_CONFIG()), + lz::Packet::ERROR::INVALID_MESSAGE + ); +} + +(int, slice) channelSend::revert::messageNotLinkedList(cell $storage) impure { + cell innerMessageCell = begin_cell().store_ones(MAX_CELL_WHOLE_BYTE_BITS).end_cell(); + cell $lzSend = MOCK_LZ_SEND() + .cl::set( + md::LzSend::packet, + MOCK_PACKET_WITH_MESSAGE( + begin_cell() + .store_ones(MAX_CELL_WHOLE_BYTE_BITS) + .store_ref(innerMessageCell) + .store_ref(innerMessageCell) + .end_cell() + ) + ); + + return test::handler::shouldFail( + channelSend, + md::MdObj::New($lzSend, MOCK_SML_SEND_EP_CONFIG()), + lz::Packet::ERROR::INVALID_MESSAGE + ); +} + +(int, slice) channelSend::revert::messageInnerNodeNotFilled(cell $storage) impure { + cell tail = begin_cell().store_ones(MAX_CELL_WHOLE_BYTE_BITS).end_cell(); + cell $lzSend = MOCK_LZ_SEND() + .cl::set( + md::LzSend::packet, + MOCK_PACKET_WITH_MESSAGE( + begin_cell() + .store_ones(MAX_CELL_WHOLE_BYTE_BITS - 8) + .store_ref(tail) + .end_cell() + ) + ); + + return test::handler::shouldFail( + channelSend, + md::MdObj::New($lzSend, MOCK_SML_SEND_EP_CONFIG()), + lz::Packet::ERROR::INVALID_MESSAGE + ); +} + +(int, slice) channelSend::success::sendMsglibNotSet(cell $storage) impure { + cell $epConfigOApp = MOCK_SML_SEND_EP_CONFIG() + .cl::set(lz::SendEpConfig::sendMsglib, NULLADDRESS); + + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New($lzSend, $epConfigOApp); + + return test::handler::shouldPass( + channelSend, + $mdObj, + unsafeTuple([ + 0, + _newAction( + SRC_OAPP, + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + $lzSend, + 0, + 0, + Channel::ERROR::MsglibBlocked + ), + getInitialStorage() + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) channelSend::success::sendMsglibConnectionNotSet(cell $storage) impure { + cell $epConfigOApp = MOCK_SML_SEND_EP_CONFIG() + .cl::set(lz::SendEpConfig::sendMsglibConnection, NULLADDRESS); + + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New($lzSend, $epConfigOApp); + + return test::handler::shouldPass( + channelSend, + $mdObj, + unsafeTuple([ + 0, + _newAction( + SRC_OAPP, + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + $lzSend, + 0, + 0, + Channel::ERROR::MsglibBlocked + ), + getInitialStorage() + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) channelSend::success::sendMsglibAndConnectionNotSet(cell $storage) impure { + cell $epConfigOApp = MOCK_SML_SEND_EP_CONFIG() + .cl::set(lz::SendEpConfig::sendMsglib, NULLADDRESS) + .cl::set(lz::SendEpConfig::sendMsglibConnection, NULLADDRESS); + + cell $lzSend = MOCK_LZ_SEND(); + cell $mdObj = md::MdObj::New($lzSend, $epConfigOApp); + + return test::handler::shouldPass( + channelSend, + $mdObj, + unsafeTuple([ + 0, + _newAction( + SRC_OAPP, + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New( + $lzSend, + 0, + 0, + Channel::ERROR::MsglibBlocked + ), + getInitialStorage() + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) forceAbort::success::basic(cell $storage) impure { + int sendRequestId = $storage.cl::get(Channel::lastSendRequestId) + 1; + cell $lzSend = MOCK_LZ_SEND_WITH_ID(sendRequestId); + + cell $mdObj = md::MdObj::New(MOCK_LZ_SEND(), MOCK_SML_SEND_EP_CONFIG()); + channelSend($mdObj); + $storage = getContractStorage(); + + cell expectedSendQueue = DeterministicInsertionCircularQueue::delete( + $storage.cl::get(Channel::sendRequestQueue), + sendRequestId + ); + + return test::handler::shouldPass( + forceAbort, + $lzSend, + unsafeTuple([ + 0, + _newAction( + SRC_OAPP, + Layerzero::OP::CHANNEL_SEND_CALLBACK, + md::MdObj::New( + md::MessagingReceipt::New($lzSend, 0, 0, Channel::ERROR::sendAborted), + getInitialStorage() + ) + ) + ]), + $storage.cl::set(Channel::sendRequestQueue, expectedSendQueue), + txnContext + ); +} + +(int, slice) forceAbort::revert::noHash(cell $storage) impure { + return test::handler::shouldFail( + forceAbort, + MOCK_LZ_SEND(), + Channel::ERROR::cannotAbortSend + ); +} + +(int, slice) forceAbort::revert::wrongHash(cell $storage) impure { + int sendRequestId = $storage.cl::get(Channel::lastSendRequestId) + 1; + cell $lzSend = MOCK_LZ_SEND_WITH_ID(sendRequestId); + + cell $mdObj = md::MdObj::New(MOCK_LZ_SEND(), MOCK_SML_SEND_EP_CONFIG()); + channelSend($mdObj); + $storage = getContractStorage(); + + cell $wrongLzSend = $lzSend.cl::set(md::LzSend::nativeFee, NATIVE_FEE + 1); + + return test::handler::shouldFail( + forceAbort, + $wrongLzSend, + Channel::ERROR::cannotAbortSend + ); +} + +(int, slice) CoinsAmount::success::basic(cell $storage) impure { + cell $depositZro = md::CoinsAmount::New(ZRO_FEE); + + cell $expectedStorage = $storage.cl::set( + Channel::zroBalance, + $storage.cl::get(Channel::zroBalance) + ZRO_FEE + ); + + return test::handler::shouldPass( + depositZro, + $depositZro, + unsafeTuple([ + 0, + _newAction(Channel::event::ZRO_DEPOSITED, $depositZro) + ]), + $expectedStorage, + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + ;; -- channel send + .tpush([channelSend::success::basic, "channelSend::success::basic"]) + .tpush([channelSend::success::sendQueueMax, "channelSend::success::sendQueueMax"]) + .tpush([channelSend::success::sendQueueCongested, "channelSend::success::sendQueueCongested"]) + .tpush([channelSend::success::maxSizeMessage, "channelSend::success::maxSizeMessage"]) + .tpush([channelSend::revert::messageTooBig, "channelSend::revert::messageTooBig"]) + .tpush([channelSend::revert::messageNotByteAligned, "channelSend::revert::messageNotByteAligned"]) + .tpush([channelSend::revert::messageNotLinkedList, "channelSend::revert::messageNotLinkedList"]) + .tpush([channelSend::revert::messageInnerNodeNotFilled, "channelSend::revert::messageInnerNodeNotFilled"]) + .tpush([channelSend::success::sendMsglibNotSet, "channelSend::success::sendMsglibNotSet"]) + .tpush([channelSend::success::sendMsglibConnectionNotSet, "channelSend::success::sendMsglibConnectionNotSet"]) + .tpush([channelSend::success::sendMsglibAndConnectionNotSet, "channelSend::success::sendMsglibAndConnectionNotSet"]) + ;; -- force abort + .tpush([forceAbort::success::basic, "forceAbort::success::basic"]) + .tpush([forceAbort::revert::noHash, "forceAbort::revert::noHash"]) + .tpush([forceAbort::revert::wrongHash, "forceAbort::revert::wrongHash"]) + ;; ;; -- zro management + .tpush([CoinsAmount::success::basic, "CoinsAmount::success::basic"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelSerde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelSerde.fc new file mode 100644 index 00000000..4b24b6c1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/channelSerde.fc @@ -0,0 +1,368 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../funC++/testutils.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::Serde"; } + +;; Channel: Has 7 getters, +;; Has 5 multi-getter (deserializer), +;; Has 7 setters + +(int, slice) Serde::Channel::getBaseStorage(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return test::getRef::equal( + $initChannel, + Channel::getBaseStorage, + Channel::baseStorage + ); +} + +(int, slice) Serde::Channel::getPath(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return test::getRef::equal( + $initChannel, + Channel::getPath, + Channel::path + ); +} + +(int, slice) Serde::Channel::getEndpointAddress(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return test::getData::equal( + $initChannel, + Channel::getEndpointAddress, + Channel::endpointAddress + ); +} + +(int, slice) Serde::Channel::getCommitPOOO(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return test::getRef::equal( + $initChannel, + Channel::getCommitPOOO, + Channel::commitPOOO + ); +} + +(int, slice) Serde::Channel::getExecutePOOO(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return test::getRef::equal( + $initChannel, + Channel::getExecutePOOO, + Channel::executePOOO + ); +} + +(int, slice) Serde::Channel::getExecutionQueue(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return test::getRef::equal( + $initChannel, + Channel::getExecutionQueue, + Channel::executionQueue + ); +} + +(int, slice) Serde::Channel::getZroBalance(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return test::getData::equal( + $initChannel, + Channel::getZroBalance, + Channel::zroBalance + ); +} + +(int, slice) Serde::Channel::getCommitPacketInformation(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS) + .cl::set(Channel::commitPOOO, POOO::New().POOO::set(1)) + .cl::set(Channel::executePOOO, POOO::New().POOO::set(2)); + + ( + cell epConfigOApp, + cell commitPOOO, + cell executePOOO, + cell executionQueue + ) = Channel::getCommitPacketInformation($initChannel); + + return test::multiget::equal( + $initChannel, + unsafeTuple([ + Channel::epConfigOApp, + Channel::commitPOOO, + Channel::executePOOO, + Channel::executionQueue + ]), + unsafeTuple([epConfigOApp, commitPOOO, executePOOO, executionQueue]) + ); +} + +(int, slice) Serde::Channel::getExecutePOOOAndExecuionQueueAndPath(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS) + .cl::set(Channel::executePOOO, POOO::New().POOO::set(1)); + + ( + cell executePOOO, + cell executionQueue, + cell path + ) = Channel::getExecutePOOOAndExecutionQueueAndPath($initChannel); + + return test::multiget::equal( + $initChannel, + unsafeTuple([ + Channel::executePOOO, + Channel::executionQueue, + Channel::path + ]), + unsafeTuple([executePOOO, executionQueue, path]) + ); +} + +(int, slice) Serde::Channel::getSendInformation(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + ( + cell epConfigOApp, + cell path, + cell sendRequestQueue, + int lastSendRequestId + ) = Channel::getSendInformation($initChannel); + + return test::multiget::equal( + $initChannel, + unsafeTuple([ + Channel::epConfigOApp, + Channel::path, + Channel::sendRequestQueue, + Channel::lastSendRequestId + ]), + unsafeTuple([epConfigOApp, path, sendRequestQueue, lastSendRequestId]) + ); +} + +(int, slice) Serde::Channel::getSendCallbackInformation(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS) + .cl::set(Channel::outboundNonce, 1); + + ( + cell sendRequestQueue, + int zroBalance, + cell path, + int outboundNonce + ) = Channel::getSendCallbackInformation($initChannel); + + return test::multiget::equal( + $initChannel, + unsafeTuple([ + Channel::sendRequestQueue, + Channel::zroBalance, + Channel::path, + Channel::outboundNonce + ]), + unsafeTuple([sendRequestQueue, zroBalance, path, outboundNonce]) + ); +} + +(int, slice) Serde::Channel::getLzReceiveLockInformation(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS) + .cl::set(Channel::commitPOOO, POOO::New().POOO::set(1)); + + ( + cell executionQueue, + cell commitPOOO, + cell path + ) = Channel::getLzReceiveLockInformation($initChannel); + + return test::multiget::equal( + $initChannel, + unsafeTuple([ + Channel::executionQueue, + Channel::commitPOOO, + Channel::path + ]), + unsafeTuple([executionQueue, commitPOOO, path]) + ); +} + +(int, slice) Serde::Channel::setSendRequestQueue(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + cell newSendRequestQueue = _getRandomCode(121); + + cell $expectedChannel = $initChannel + .cl::set(Channel::sendRequestQueue, newSendRequestQueue); + + cell $optimizedChannel = Channel::setSendRequestQueue( + $initChannel, + newSendRequestQueue + ); + + return test::set::equal( + $expectedChannel, + $optimizedChannel + ); +} + +(int, slice) Serde::Channel::setExecutePOOO(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + cell newExecutePOOO = _getRandomCode(120); + + cell $expectedChannel = $initChannel + .cl::set(Channel::executePOOO, newExecutePOOO); + + cell $optimizedChannel = Channel::setExecutePOOO( + $initChannel, + newExecutePOOO + ); + + return test::set::equal( + $expectedChannel, + $optimizedChannel + ); +} + +(int, slice) Serde::Channel::setExecutionQueue(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + cell newExecutionQueue = _getRandomCode(127); + + cell $expectedChannel = $initChannel + .cl::set(Channel::executionQueue, newExecutionQueue); + + cell $optimizedChannel = Channel::setExecutionQueue( + $initChannel, + newExecutionQueue + ); + + return test::set::equal( + $expectedChannel, + $optimizedChannel + ); +} + +(int, slice) Serde::Channel::setSendRequestQueueAndLastSendRequestId(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + int newLastSendRequestId = 124; + cell newSendRequestQueue = _getRandomCode(125); + + cell $expectedChannel = $initChannel + .cl::set(Channel::lastSendRequestId, newLastSendRequestId) + .cl::set(Channel::sendRequestQueue, newSendRequestQueue); + + cell $optimizedChannel = Channel::setSendRequestQueueAndLastSendRequestId( + $initChannel, + newLastSendRequestId, + newSendRequestQueue + ); + + return test::set::equal( + $expectedChannel, + $optimizedChannel + ); +} + +(int, slice) Serde::Channel::setOutboundNonceAndZroBalance(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + int newOutboundNonce = 124; + int newZroBalance = 125; + + cell $expectedChannel = $initChannel + .cl::set(Channel::outboundNonce, newOutboundNonce) + .cl::set(Channel::zroBalance, newZroBalance); + + cell $optimizedChannel = Channel::setOutboundNonceAndZroBalance( + $initChannel, + newOutboundNonce, + newZroBalance + ); + + return test::set::equal( + $expectedChannel, + $optimizedChannel + ); +} + +(int, slice) Serde::Channel::setCommitPOOOAndExecutionQueue(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + cell newCommitPOOO = _getRandomCode(126); + cell newExecutionQueue = _getRandomCode(127); + + cell $expectedChannel = $initChannel + .cl::set(Channel::commitPOOO, newCommitPOOO) + .cl::set(Channel::executionQueue, newExecutionQueue); + + cell $optimizedChannel = Channel::setCommitPOOOAndExecutionQueue( + $initChannel, + newCommitPOOO, + newExecutionQueue + ); + + return test::set::equal( + $expectedChannel, + $optimizedChannel + ); +} + +(int, slice) Serde::Channel::setPath(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + cell newPath = MOCK_RECEIVE_PATH(); + + cell $expectedChannel = $initChannel + .cl::set(Channel::path, newPath); + + cell $optimizedChannel = Channel::setPath( + $initChannel, + newPath + ); + + return test::set::equal( + $expectedChannel, + $optimizedChannel + ); +} + +(int, slice) Serde::Channel::sanitize(cell $unused) impure { + cell $initChannel = Channel::New(CONTROLLER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + return test::build::equal( + $initChannel, + Channel::sanitize(_dupWithGarbage($initChannel)) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::Channel::getBaseStorage, "Serde::Channel::getBaseStorage"]) + .tpush([Serde::Channel::getPath, "Serde::Channel::getPath"]) + .tpush([Serde::Channel::getEndpointAddress, "Serde::Channel::getEndpointAddress"]) + .tpush([Serde::Channel::getCommitPOOO, "Serde::Channel::getCommitPOOO"]) + .tpush([Serde::Channel::getExecutePOOO, "Serde::Channel::getExecutePOOO"]) + .tpush([Serde::Channel::getExecutionQueue, "Serde::Channel::getExecutionQueue"]) + .tpush([Serde::Channel::getZroBalance, "Serde::Channel::getZroBalance"]) + .tpush([Serde::Channel::getCommitPacketInformation, "Serde::Channel::getCommitPacketInformation"]) + .tpush([Serde::Channel::getExecutePOOOAndExecuionQueueAndPath, "Serde::Channel::getExecutePOOOAndExecuionQueueAndPath"]) + .tpush([Serde::Channel::getSendInformation, "Serde::Channel::getSendInformation"]) + .tpush([Serde::Channel::getSendCallbackInformation, "Serde::Channel::getSendCallbackInformation"]) + .tpush([Serde::Channel::getLzReceiveLockInformation, "Serde::Channel::getLzReceiveLockInformation"]) + + .tpush([Serde::Channel::setSendRequestQueue, "Serde::Channel::setSendRequestQueue"]) + .tpush([Serde::Channel::setExecutePOOO, "Serde::Channel::setExecutePOOO"]) + .tpush([Serde::Channel::setExecutionQueue, "Serde::Channel::setExecutionQueue"]) + + .tpush([Serde::Channel::setSendRequestQueueAndLastSendRequestId, "Serde::Channel::setSendRequestQueueAndLastSendRequestId"]) + .tpush([Serde::Channel::setOutboundNonceAndZroBalance, "Serde::Channel::setOutboundNonceAndZroBalance"]) + .tpush([Serde::Channel::setCommitPOOOAndExecutionQueue, "Serde::Channel::setCommitPOOOAndExecutionQueue"]) + .tpush([Serde::Channel::setPath, "Serde::Channel::setPath"]) + .tpush([Serde::Channel::sanitize, "Serde::Channel::sanitize"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/permissions.fc new file mode 100644 index 00000000..02ea47ae --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/channel/tests/permissions.fc @@ -0,0 +1,230 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../interfaces.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Channel::permissions"; } + +cell createContractStorage() impure { + cell $storage = Channel::New( + getCaller(), + MOCK_SEND_PATH(), + ENDPOINT_ADDRESS + ); + + ;; set the default send lib address to allow testing send lib etc. + cell $epConfigOApp = $storage.cl::get(Channel::epConfigOApp); + $epConfigOApp = $epConfigOApp.cl::set(lz::EpConfig::sendMsglib, SEND_MSGLIB_ADDRESS); + + setContractStorage($storage.cl::set(Channel::epConfigOApp, $epConfigOApp)); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS=============================== + +(int, slice) checkPermissions::lzReceivePrepare::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass(Channel::OP::LZ_RECEIVE_PREPARE, cl::nullObject()); +} + +(int, slice) checkPermissions::lzReceiveLock::success::basic(cell $storage) impure { + ;; SRC_OAPP of the channel is the DST_OAPP of the packet in this receive context + spoofCaller(SRC_OAPP); + return test::permissions::shouldPass(Channel::OP::LZ_RECEIVE_LOCK, cl::nullObject()); +} + +(int, slice) checkPermissions::lzReceiveLock::revert::notOApp(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Channel::OP::LZ_RECEIVE_LOCK, cl::nullObject()); +} + +(int, slice) checkPermissions::lzReceiveExecuteCallback::success::basic(cell $storage) impure { + ;; SRC_OAPP of the channel is the DST_OAPP of the packet in this receive context + spoofCaller(SRC_OAPP); + return test::permissions::shouldPass(Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK, cl::nullObject()); +} + +(int, slice) checkPermissions::lzReceiveExecuteCallback::revert::notOApp(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Channel::OP::LZ_RECEIVE_EXECUTE_CALLBACK, cl::nullObject()); +} + +(int, slice) checkPermissions::CoinsAmount::success::basic(cell $storage) impure { + return test::permissions::shouldPass(Channel::OP::DEPOSIT_ZRO, cl::nullObject()); +} + +(int, slice) checkPermissions::CoinsAmount::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Channel::OP::DEPOSIT_ZRO, cl::nullObject()); +} + +(int, slice) checkPermissions::setEpConfigOApp::success::basic(cell $storage) impure { + spoofCaller(ENDPOINT_ADDRESS); + return test::permissions::shouldPass(Channel::OP::SET_EP_CONFIG_OAPP, cl::nullObject()); +} + +(int, slice) checkPermissions::setEpConfigOApp::revert::notEndpoint(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Channel::OP::SET_EP_CONFIG_OAPP, cl::nullObject()); +} + +(int, slice) checkPermissions::channelSend::success::basic(cell $storage) impure { + spoofCaller(ENDPOINT_ADDRESS); + return test::permissions::shouldPass(Channel::OP::CHANNEL_SEND, cl::nullObject()); +} + +(int, slice) checkPermissions::channelSend::revert::notEndpoint(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Channel::OP::CHANNEL_SEND, cl::nullObject()); +} + +(int, slice) checkPermissions::channelCommitPacket::success::nonce1(cell $storage) impure { + spoofCaller(ENDPOINT_ADDRESS); + return test::permissions::shouldPass(Channel::OP::CHANNEL_COMMIT_PACKET, cl::nullObject()); +} + +(int, slice) checkPermissions::channelCommitPacket::revert::notEndpoint(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Channel::OP::CHANNEL_COMMIT_PACKET, cl::nullObject()); +} + +(int, slice) checkPermissions::msglibSendCallback::success::basic(cell $storage) impure { + spoofCaller(SEND_MSGLIB_ADDRESS); + return test::permissions::shouldPass( + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK() + ); +} + +(int, slice) checkPermissions::msglibSendCallback::revert::notSendMsglib(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK() + ); +} + +(int, slice) checkPermissions::nilify::success::basic(cell $storage) impure { + spoofCaller(SRC_OAPP); + return test::permissions::shouldPass(Channel::OP::NILIFY, cl::nullObject()); +} + +(int, slice) checkPermissions::nilify::revert::notOApp(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Channel::OP::NILIFY, cl::nullObject()); +} + +(int, slice) checkPermissions::burn::success::basic(cell $storage) impure { + spoofCaller(SRC_OAPP); + return test::permissions::shouldPass(Channel::OP::NILIFY, cl::nullObject()); +} + +(int, slice) checkPermissions::burn::revert::notOApp(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Channel::OP::NILIFY, cl::nullObject()); +} + +(int, slice) checkPermissions::forceAbort::success::basic(cell $storage) impure { + spoofCaller(SRC_OAPP); + return test::permissions::shouldPass(Channel::OP::FORCE_ABORT, cl::nullObject()); +} + +(int, slice) checkPermissions::forceAbort::revert::notOApp(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Channel::OP::FORCE_ABORT, cl::nullObject()); +} + +(int, slice) checkPermissions::syncMsglibConnection::success::basic(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldPass( + Channel::OP::SYNC_MSGLIB_CONNECTION, + md::MdAddress::New(MOCK_SEND_PATH(), MSGLIB_CONNECTION_ADDRESS) + ); +} + +(int, slice) checkPermissions::notifyPacketExecuted::success::basic(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldPass( + Channel::OP::NOTIFY_PACKET_EXECUTED, + md::MdAddress::New(md::Nonce::New(NONCE), MSGLIB_CONNECTION_ADDRESS) + ); +} + +(int, slice) checkPermissions::emitPacketStatus::success::basic(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldPass( + Channel::OP::EMIT_LZ_RECEIVE_ALERT, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::default::revert::invalidOpCode(cell $storage) impure { + ;; this is a generic opcode that we should NOT allow + return test::permissions::shouldFail(OP::RANDOM, cl::nullObject()); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- Lz Receive Prepare + .tpush([checkPermissions::lzReceivePrepare::success::basic, "checkPermissions::lzReceivePrepare::success::basic"]) + ;; -- Lz Receive Lock + .tpush([checkPermissions::lzReceiveLock::success::basic, "checkPermissions::lzReceiveLock::success::basic"]) + .tpush([checkPermissions::lzReceiveLock::revert::notOApp, "checkPermissions::lzReceiveLock::revert::notOApp"]) + ;; -- Lz Receive Execute Callback + .tpush([checkPermissions::lzReceiveExecuteCallback::success::basic, "checkPermissions::lzReceiveExecuteCallback::success::basic"]) + .tpush([checkPermissions::lzReceiveExecuteCallback::revert::notOApp, "checkPermissions::lzReceiveExecuteCallback::revert::notOApp"]) + ;; -- Lz Receive deposit Zro + .tpush([checkPermissions::CoinsAmount::success::basic, "checkPermissions::CoinsAmount::success::basic"]) + .tpush([checkPermissions::CoinsAmount::revert::notOwner, "checkPermissions::CoinsAmount::revert::notOwner"]) + ;; -- set ep config oapp + .tpush([checkPermissions::setEpConfigOApp::success::basic, "checkPermissions::setEpConfigOApp::success::basic"]) + .tpush([checkPermissions::setEpConfigOApp::revert::notEndpoint, "checkPermissions::setEpConfigOApp::revert::notEndpoint"]) + ;; -- Channel Send + .tpush([checkPermissions::channelSend::success::basic, "checkPermissions::channelSend::success::basic"]) + .tpush([checkPermissions::channelSend::revert::notEndpoint, "checkPermissions::channelSend::revert::notEndpoint"]) + ;; -- Channel Commit Packet + .tpush([checkPermissions::channelCommitPacket::success::nonce1, "checkPermissions::channelCommitPacket::success::nonce1"]) + .tpush([checkPermissions::channelCommitPacket::revert::notEndpoint, "checkPermissions::channelCommitPacket::revert::notEndpoint"]) + ;; -- Msglib send callback + .tpush([checkPermissions::msglibSendCallback::success::basic, "checkPermissions::msglibSendCallback::success::basic"]) + .tpush([checkPermissions::msglibSendCallback::revert::notSendMsglib, "checkPermissions::msglibSendCallback::revert::notSendMsglib"]) + ;; -- Nilify + .tpush([checkPermissions::nilify::success::basic, "checkPermissions::nilify::success::basic"]) + .tpush([checkPermissions::nilify::revert::notOApp, "checkPermissions::nilify::revert::notOApp"]) + ;; -- Burn + .tpush([checkPermissions::burn::success::basic, "checkPermissions::burn::success::basic"]) + .tpush([checkPermissions::burn::revert::notOApp, "checkPermissions::burn::revert::notOApp"]) + ;; -- Force abort + .tpush([checkPermissions::forceAbort::success::basic, "checkPermissions::forceAbort::success::basic"]) + .tpush([checkPermissions::forceAbort::revert::notOApp, "checkPermissions::forceAbort::revert::notOApp"]) + ;; -- Msglib Sync connection + .tpush([checkPermissions::syncMsglibConnection::success::basic, "checkPermissions::syncMsglibConnection::success::basic"]) + ;; -- Notify packet Executed + .tpush([checkPermissions::notifyPacketExecuted::success::basic, "checkPermissions::notifyPacketExecuted::success::basic"]) + ;; -- emitPacketStatus + .tpush([checkPermissions::emitPacketStatus::success::basic, "checkPermissions::emitPacketStatus::success::basic"]) + ;; -- invalid opcode + .tpush([checkPermissions::default::revert::invalidOpCode, "checkPermissions::default::revert::invalidOpCode"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/handler.fc new file mode 100644 index 00000000..2eaf1873 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/handler.fc @@ -0,0 +1,390 @@ +;;; =============================================================== +;; The controller is only used for deployment and configuration. +;; Controller handlers are not necessarily gas efficient, as they +;; are expected to be called infrequently. +;; No dictionaries exist in the controller, so there is no risk of +;; gas usage increasing over time and resulting in DoS due to the 1m gas +;; limit. +;;; =============================================================== + +#include "../core/abstract/protocolHandler.fc"; + +#include "../../classes/lz/Path.fc"; + +#include "../../classes/msgdata/Deploy.fc"; +#include "../../classes/msgdata/ExtendedMd.fc"; +#include "../../classes/msgdata/CoinsAmount.fc"; +#include "../../classes/msgdata/AddMsglib.fc"; +#include "../../classes/msgdata/InitEndpoint.fc"; +#include "../../classes/msgdata/MdEid.fc"; +#include "../../classes/msgdata/MdObj.fc"; +#include "../../classes/msgdata/SetAddress.fc"; + +#include "../core/baseStorage.fc"; + +#include "../channel/storage.fc"; +#include "../endpoint/storage.fc"; +#include "../interfaces.fc"; + +#include "interface.fc"; +#include "storage.fc"; +#include "../../classes/msgdata/SetEpConfig.fc"; + +;;; ================INTERFACE FUNCTIONS===================== + +(cell, tuple) _initialize(cell $md) impure inline { + return preamble(); +} + +;; should be the protocol owner's zro wallet +int _getZroWalletAddress() inline method_id { + return getContractStorage().cl::get
(Controller::zroWallet); +} + +() assertTentativeOwner() impure inline { + throw_unless( + Controller::ERROR::onlyTentativeOwner, + getCaller() == getContractStorage().cl::get
(Controller::tentativeOwner) + ); +} + +int _getEventSink() inline { + return getContractAddress(); +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + if (op == Controller::OP::DEPLOY_CHANNEL) { + ;; Controller::OP::DEPLOY_CHANNEL determines the initial storage of + ;; the deployed channel by the caller address, making this opcode + ;; safe for open and public calls + return (); + } elseif (op == Controller::OP::SET_EP_CONFIG_OAPP) { + cell $path = $md.cl::get(md::ExtendedMd::obj).lz::Path::sanitize(); + throw_unless( + Controller::ERROR::onlyOApp, + getCaller() == $path.cl::get
(lz::Path::srcOApp) + ); + } elseif ( + (op == Controller::OP::DEPLOY_ENDPOINT) + | (op == Controller::OP::ADD_MSGLIB) + | (op == Controller::OP::SET_EP_CONFIG_DEFAULTS) + | (op == Controller::OP::SET_ZRO_WALLET) + | (op == Controller::OP::TRANSFER_OWNERSHIP) + ) { + return assertOwner(); + } elseif (op == Controller::OP::CLAIM_OWNERSHIP) { + return assertTentativeOwner(); + } elseif (op == Controller::OP::EXCESSES) { + throw_unless(Controller::ERROR::onlyZroWallet, getCaller() == _getZroWalletAddress()); + } else { + ;; Any other opcodes will throw, to limit possibility of unexpected behaviors + throw(BaseInterface::ERROR::invalidOpcode); + } +} + +;;; ==========================HELPER FUNCTIONS===================================== + +int _calculateEndpointAddress(int dstEid) impure inline method_id { + throw_if(Controller::ERROR::invalidEid, dstEid == 0); + + return computeContractAddress( + Endpoint::New( + getContractStorage().cl::get(Controller::eid), + dstEid, + getContractAddress() + ), + getContractStorage().cl::get(Controller::endpointCode) + ); +} + +;;; ==========================VIEW FUNCTIONS===================================== + +;; _verifyEventSender is used by offchain entities to verify that an event emitted +;; by the controller was sent by a protocol contract (endpoint or channel). +;; Each event transaction includes the storage init of the event emitting contract, +;; which is used to verify the sender is the contract they claim to be. + +;; Returns true if the event originates from a protocol contract. +;; returns false if the event does not originate from a protocol contract. +int _verifyEventSender(int sender, cell $senderStorageInit) impure method_id { + cell $storage = getContractStorage(); + + if (sender == getContractAddress()) { + return true; + } + + int storageType = cl::typeof($senderStorageInit); + int senderOwner = NULLADDRESS; + + if (storageType == Endpoint::NAME) { + cell $endpointStorageInit = Endpoint::sanitize($senderStorageInit); + throw_if( + BaseInterface::ERROR::invalidEventSource, + $endpointStorageInit.cl::hash() != $senderStorageInit.cl::hash() + ); + + cell endpointCode = $storage.cl::get(Controller::endpointCode); + throw_unless( + BaseInterface::ERROR::invalidEventSource, + sender == computeContractAddress($senderStorageInit, endpointCode) + ); + + senderOwner = $endpointStorageInit + .cl::get(Endpoint::baseStorage) + .cl::get
(BaseStorage::owner); + + } elseif (storageType == Channel::NAME) { + cell $channelStorageInit = Channel::sanitize($senderStorageInit); + throw_if( + BaseInterface::ERROR::invalidEventSource, + $channelStorageInit.cl::hash() != $senderStorageInit.cl::hash() + ); + + cell channelCode = $storage.cl::get(Controller::channelCode); + throw_unless( + BaseInterface::ERROR::invalidEventSource, + sender == computeContractAddress($senderStorageInit, channelCode) + ); + + senderOwner = $channelStorageInit + .cl::get(Channel::baseStorage) + .cl::get
(BaseStorage::owner); + } else { + throw(BaseInterface::ERROR::invalidEventSource); + } + + ;; If senderOwner != controller address, then this event sender might be using the correct + ;; bytecode and storage but be part of a different LayerZero protocol deployment owned + ;; by a different signer. + throw_if(BaseInterface::ERROR::invalidEventSource, senderOwner != getContractAddress()); + + return true; +} + +;;; ==========================HANDLERS===================================== + +;; @in owner EOA +;; @in_md deployEndpoint +;; @out endpoint/handler.fc +;; @out_md deployAndCall(endpointInitData, endpointCode, endpoint_initialize) +;; @permissions only owner +tuple deployEndpoint(cell $deploy) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedDeploy = $deploy.md::Deploy::sanitize(); + int dstEid = $sanitizedDeploy.cl::get(md::Deploy::dstEid); + throw_if(Controller::ERROR::invalidEid, dstEid == 0); + + int eid = $storage.cl::get(Controller::eid); + + actions~pushAction( + $storage.cl::get(Controller::endpointCode), + Endpoint::New(eid, dstEid, getContractAddress()), + $sanitizedDeploy.cl::get(md::Deploy::initialDeposit), + BaseInterface::OP::INITIALIZE, + md::InitEndpoint::New($storage.cl::get(Controller::channelCode)), + 0 + ); + + return actions; +} + +;; @in oApp +;; @in_md deployChannel +;; @out channel/handler.fc +;; @out_md deployAndCall(endpoint_state_init, endpointCode, channel_initialize) +;; @note: There's no need to check for permissions here because ctx.SENDER is derived from the oApp. +;; @permissionless +tuple deployChannel(cell $deploy) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedDeploy = $deploy.md::Deploy::sanitize(); + int dstEid = $sanitizedDeploy.cl::get(md::Deploy::dstEid); + + ;; create the path object + cell $path = lz::Path::New( + $storage.cl::get(Controller::eid), ;; srcEid + getCaller(), ;; srcOApp + dstEid, ;; dstEid + $sanitizedDeploy.cl::get(md::Deploy::dstOApp) ;; dstOApp + ); + + ;; get the endpoint address + int endpointAddress = _calculateEndpointAddress(dstEid); + + actions~pushAction( + $storage.cl::get(Controller::channelCode), + Channel::New(getContractAddress(), $path, endpointAddress), + $sanitizedDeploy.cl::get(md::Deploy::initialDeposit), + BaseInterface::OP::INITIALIZE, + cl::nullObject(), + 0 + ); + + return actions; +} + +;; @in owner EOA +;; @in_md setEpConfigDefaults +;; @out endpoint/handler.fc/setEpConfigDefaults +;; @out_md setEpConfig +;; @permissions only owner +tuple setEpConfigDefaults(cell $mdEid) impure inline method_id { + (_, tuple actions) = preamble(); + + $mdEid = $mdEid.md::MdEid::sanitize(); + + int dstEid = $mdEid.cl::get(md::MdEid::eid); + cell $sanitizedSetEpConfig = $mdEid.cl::get(md::MdEid::md).md::SetEpConfig::sanitize(); + + actions~pushAction( + _calculateEndpointAddress(dstEid), + Endpoint::OP::SET_EP_CONFIG_DEFAULTS, + $sanitizedSetEpConfig + ); + + return actions; +} + +;; @in OApp contract +;; @in_md ExtendedMd(path, setEpConfig) +;; @out endpoint/handler.fc/setEpConfig +;; @out_md ExtendedMd(channelAddress, setEpConfig) +;; @note: There's no need to check for permissions here because ctx.SENDER is derived from the oApp. +;; Any configuration changes made will only impact the `oApp` that invokes this function. +;; @permissionless +tuple setEpConfigOApp(cell $mdObj) impure inline method_id { + (_, tuple actions) = preamble(); + + ;; get the path and setEpConfigMd from the md + cell $sanitizedPath = $mdObj.cl::get(md::MdObj::obj).lz::Path::sanitize(); + + ;; create a sanitized mdObj with the sanitized setEpConfig + $mdObj = md::MdObj::New( + $mdObj.cl::get(md::MdObj::md).md::SetEpConfig::sanitize(), + $sanitizedPath + ); + + actions~pushAction( + _calculateEndpointAddress($sanitizedPath.cl::get(lz::Path::dstEid)), + Endpoint::OP::SET_EP_CONFIG_OAPP, + $mdObj + ); + + return actions; +} + +tuple addMsglib(cell $addMsglibMd) impure inline method_id { + (_, tuple actions) = preamble(); + + cell $sanitizedAddMsglibMd = $addMsglibMd.md::AddMsglib::sanitize(); + + actions~pushAction( + _calculateEndpointAddress( + $sanitizedAddMsglibMd.cl::get(md::AddMsglib::dstEid) + ), + Endpoint::OP::ADD_MSGLIB, + $sanitizedAddMsglibMd + ); + + return actions; +} + +;; only controller zro wallet +tuple depositZro(cell $path) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedPath = $path.lz::Path::sanitize(); + + ;; The message format of jetton happens to align with funC++ + ;; but the jetton token amount is aliased as donationNanos + int tokenAmount = getDonationNanos(); + setDonationNanos(0); + + ;; get the endpoint address + int endpointAddress = _calculateEndpointAddress($sanitizedPath.cl::get(lz::Path::dstEid)); + + int channelAddress = computeContractAddress( + Channel::New(getContractAddress(), $sanitizedPath, endpointAddress), + $storage.cl::get(Controller::channelCode) + ); + + actions~pushAction( + channelAddress, + Channel::OP::DEPOSIT_ZRO, + md::CoinsAmount::New(tokenAmount) + ); + + return actions; +} + +tuple setZroWallet(cell $setAddress) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedSetAddress = $setAddress.md::SetAddress::sanitize(); + + setContractStorage( + $storage.cl::set( + Controller::zroWallet, + $sanitizedSetAddress.cl::get
(md::SetAddress::address) + ) + ); + + actions~pushAction(Controller::event::ZRO_WALLET_SET, $sanitizedSetAddress); + return actions; +} + +;; This will override an existing tentative owner +;; Becuase we assert that the 'claimOwnership' can't be done by the 'null' address, it means +;; if we want to burn the ownership, we will need to set it to a contract which only has ability to +;; claim, but nothing else. This effectively burns it. +tuple transferOwnership(cell $setAddress) impure inline { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedSetAddress = $setAddress.md::SetAddress::sanitize(); + + setContractStorage( + $storage.cl::set( + Controller::tentativeOwner, + $sanitizedSetAddress.cl::get
(md::SetAddress::address) + ) + ); + + actions~pushAction( + Controller::event::OWNER_SET_TENTATIVE, + $sanitizedSetAddress + ); + + return actions; +} + +tuple claimOwnership(cell $emptyMd) impure inline { + (cell $storage, tuple actions) = preamble(); + + ;; permissions check guarantees newOwner == $storage.tentativeOwner + int newOwner = $storage.cl::get
(Controller::tentativeOwner); + + ;; this should be caught by the permission check, BUT let's be super safe about this + throw_if(Controller::ERROR::nullTentativeOwner, newOwner == NULLADDRESS); + + ;; erase the tentative owner AND set that tentative owner to the actual owner + setContractStorage( + $storage + .cl::set(Controller::tentativeOwner, NULLADDRESS) + .cl::set( + Controller::baseStorage, + $storage + .cl::get(Controller::baseStorage) + .cl::set(BaseStorage::owner, newOwner) + ) + ); + + actions~pushAction( + Controller::event::OWNER_SET, + md::SetAddress::New(newOwner) + ); + + return actions; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/interface.fc new file mode 100644 index 00000000..002156d0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/interface.fc @@ -0,0 +1,25 @@ +const int MAX_MSGLIBS = 255; + +;; OPCODES +const int Controller::OP::DEPLOY_ENDPOINT = "Controller::OP::DEPLOY_ENDPOINT"c; +const int Controller::OP::DEPLOY_CHANNEL = "Controller::OP::DEPLOY_CHANNEL"c; +const int Controller::OP::SET_EP_CONFIG_DEFAULTS = "Controller::OP::SET_EP_CONFIG_DEFAULTS"c; +const int Controller::OP::SET_EP_CONFIG_OAPP = "Controller::OP::SET_EP_CONFIG_OAPP"c; +const int Controller::OP::ADD_MSGLIB = "Controller::OP::ADD_MSGLIB"c; +const int Controller::OP::DEPOSIT_ZRO = "Controller::OP::DEPOSIT_ZRO"c; +const int Controller::OP::SET_ZRO_WALLET = "Controller::OP::SET_ZRO_WALLET"c; +const int Controller::OP::EXCESSES = 0xd53276db; ;; op::excesses: 0xd53276db +const int Controller::OP::TRANSFER_OWNERSHIP = "Controller::OP::TRANSFER_OWNERSHIP"c; +const int Controller::OP::CLAIM_OWNERSHIP = "Controller::OP::CLAIM_OWNERSHIP"c; + +;; EVENT +const int Controller::event::ZRO_WALLET_SET = "Controller::event::ZRO_WALLT_SET"u; +const int Controller::event::OWNER_SET = "Controller::event::OWNER_SET"u; +const int Controller::event::OWNER_SET_TENTATIVE = "Controller::event::OWNERSET_TENT"u; + +;; ERRORS +const int Controller::ERROR::onlyOApp = 65; +const int Controller::ERROR::invalidEid = 66; +const int Controller::ERROR::onlyZroWallet = 67; +const int Controller::ERROR::onlyTentativeOwner = 68; +const int Controller::ERROR::nullTentativeOwner = 69; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/main.fc new file mode 100644 index 00000000..1ec29cd4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/main.fc @@ -0,0 +1,29 @@ +#include "../core/abstract/protocolMain.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == Controller::OP::DEPLOY_ENDPOINT) { + return deployEndpoint($md); + } elseif (op == Controller::OP::DEPLOY_CHANNEL) { + return deployChannel($md); + } elseif (op == Controller::OP::SET_EP_CONFIG_DEFAULTS) { + return setEpConfigDefaults($md); + } elseif (op == Controller::OP::SET_EP_CONFIG_OAPP) { + return setEpConfigOApp($md); + } elseif (op == Controller::OP::ADD_MSGLIB) { + return addMsglib($md); + } elseif (op == Controller::OP::SET_ZRO_WALLET) { + return setZroWallet($md); + } elseif (op == Controller::OP::EXCESSES) { + return depositZro($md); + } elseif (op == Controller::OP::TRANSFER_OWNERSHIP) { + return transferOwnership($md); + } elseif (op == Controller::OP::CLAIM_OWNERSHIP) { + return claimOwnership($md); + } else { + throw(BaseInterface::ERROR::invalidOpcode); + } + return empty_tuple(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/storage.fc new file mode 100644 index 00000000..56353f49 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/storage.fc @@ -0,0 +1,31 @@ +#include "../../funC++/constants.fc"; + +#include "../core/baseStorage.fc"; + +const int Controller::NAME = "controller"u; + +const int Controller::baseStorage = 0; +const int Controller::eid = 1; + +;; Bytecodes +const int Controller::endpointCode = 2; +const int Controller::channelCode = 3; +;; Zro wallet +const int Controller::zroWallet = 4; +;; tentative owner +const int Controller::tentativeOwner = 5; + +;; @owner Protocol admin EOA +cell Controller::New(int owner, int eid, cell endpointCode, cell channelCode) impure method_id { + return cl::declare( + Controller::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; Controller::baseStorage + [cl::t::uint32, eid], ;; Controller::eid + [cl::t::cellRef, endpointCode], ;; Controller::endpointCode + [cl::t::cellRef, channelCode], ;; Controller::channelCode + [cl::t::address, NULLADDRESS], ;; Controller::zroWallet + [cl::t::address, NULLADDRESS] ;; Controller::tentativeOwner + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/assertions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/assertions.fc new file mode 100644 index 00000000..9c086470 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/assertions.fc @@ -0,0 +1,200 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../endpoint/storage.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/msgdata/MdEid.fc"; +#include "../../../classes/msgdata/SetAddress.fc"; +#include "../../../funC++/testutils.fc"; + + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "controller"; } + +cell createContractStorage() impure { + setContractStorage( + Controller::New( + getCaller(), + SRC_EID, + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE() + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(md::InitEndpoint::New(MOCK_CHANNEL_CODE())); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) _verifyEventSender::success::endpoint(cell $storage) impure { + cell $endpointStorage = Endpoint::New(SRC_EID, DST_EID, getContractAddress()); + + setContractStorage( + $storage.cl::set( + Controller::endpointCode, + MOCK_ENDPOINT_CODE() + ) + ); + + return test::shouldBeTrue( + _verifyEventSender( + computeContractAddress($endpointStorage, MOCK_ENDPOINT_CODE()), + $endpointStorage + ) + ); +} + +(int, slice) _verifyEventSender::success::channel(cell $storage) impure { + cell $channelStorage = Channel::New(getContractAddress(), MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + setContractStorage( + $storage.cl::set( + Controller::channelCode, + MOCK_CHANNEL_CODE() + ) + ); + + return test::shouldBeTrue( + _verifyEventSender( + computeContractAddress($channelStorage, MOCK_CHANNEL_CODE()), + $channelStorage + ) + ); +} + +(int, slice) _verifyEventSender::success::garbageEndpoint(cell $storage) impure { + cell $invalidStorage = Endpoint::New(SRC_EID, DST_EID, getContractAddress()); + $invalidStorage = _dupWithGarbage($invalidStorage); + + setContractStorage( + $storage.cl::set( + Controller::endpointCode, + MOCK_ENDPOINT_CODE() + ) + ); + + try { + _verifyEventSender( + computeContractAddress($invalidStorage, MOCK_ENDPOINT_CODE()), + $invalidStorage + ); + return (TEST_FAILED, "Should have thrown"); + } catch (x, n) { + return (TEST_SUCCESS, ""); + } +} + +(int, slice) _verifyEventSender::success::garbageChannel(cell $storage) impure { + cell $invalidStorage = Channel::New(getContractAddress(), MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + $invalidStorage = _dupWithGarbage($invalidStorage); + + setContractStorage( + $storage.cl::set( + Controller::channelCode, + MOCK_CHANNEL_CODE() + ) + ); + + try { + _verifyEventSender( + computeContractAddress($invalidStorage, MOCK_CHANNEL_CODE()), + $invalidStorage + ); + return (TEST_FAILED, "Should have thrown"); + } catch (x, n) { + return (TEST_SUCCESS, ""); + } +} + +(int, slice) _verifyEventSender::success::invalidStorageType(cell $storage) impure { + cell $invalidStorage = MOCK_SEND_PACKET(); + + setContractStorage( + $storage.cl::set( + Controller::channelCode, + MOCK_CHANNEL_CODE() + ) + ); + + try { + _verifyEventSender( + computeContractAddress($invalidStorage, MOCK_CHANNEL_CODE()), + $invalidStorage + ); + return (TEST_FAILED, "Should have thrown"); + } catch (x, n) { + return (TEST_SUCCESS, ""); + } +} + +(int, slice) _verifyEventSender::success::invalidController(cell $storage) impure { + cell $invalidStorage = Channel::New(ATTACKER_ADDRESS, MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + setContractStorage( + $storage.cl::set( + Controller::channelCode, + MOCK_CHANNEL_CODE() + ) + ); + + try { + _verifyEventSender( + computeContractAddress($invalidStorage, MOCK_CHANNEL_CODE()), + $invalidStorage + ); + return (TEST_FAILED, "Should have thrown"); + } catch (x, n) { + return (TEST_SUCCESS, ""); + } +} + +(int, slice) _verifyEventSender::success::invalidCaller(cell $storage) impure { + cell $validStorage = Channel::New(getContractAddress(), MOCK_SEND_PATH(), ENDPOINT_ADDRESS); + + setContractStorage( + $storage.cl::set( + Controller::channelCode, + MOCK_CHANNEL_CODE() + ) + ); + + try { + _verifyEventSender(ATTACKER_ADDRESS, $validStorage); + return (TEST_FAILED, "Should have thrown"); + } catch (x, n) { + return (TEST_SUCCESS, ""); + } +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- verify event sender + .tpush([_verifyEventSender::success::endpoint, "_verifyEventSender::success::endpoint"]) + .tpush([_verifyEventSender::success::channel, "_verifyEventSender::success::channel"]) + .tpush([_verifyEventSender::success::garbageEndpoint, "_verifyEventSender::success::garbageEndpoint"]) + .tpush([_verifyEventSender::success::garbageChannel, "_verifyEventSender::success::garbageChannel"]) + .tpush([_verifyEventSender::success::invalidStorageType, "_verifyEventSender::success::invalidStorageType"]) + .tpush([_verifyEventSender::success::invalidController, "_verifyEventSender::success::invalidController"]) + .tpush([_verifyEventSender::success::invalidCaller, "_verifyEventSender::success::invalidCaller"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/main.fc new file mode 100644 index 00000000..36b1ea09 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/main.fc @@ -0,0 +1,387 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/lz/Packet.fc"; +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; +#include "../../endpoint/storage.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../classes/msgdata/MdEid.fc"; +#include "../../../classes/msgdata/SetAddress.fc"; +#include "../../../classes/msgdata/MessagingReceipt.fc"; +#include "../../../classes/msgdata/MsglibSendCallback.fc"; +#include "../../../classes/msgdata/PacketSent.fc"; +#include "../../../classes/msgdata/LzReceiveStatus.fc"; +#include "../../../classes/msgdata/PacketId.fc"; +#include "../../../classes/msgdata/LzSend.fc"; +#include "../../../classes/msgdata/Nonce.fc"; +#include "../../../workers/msgdata/SignedRequest.fc"; +#include "../../../workers/msgdata/ExecuteParams.fc"; +#include "../../msglibs/ultralightnode/msgdata/DvnFeesPaidEvent.fc"; +#include "../../msglibs/ultralightnode/msgdata/ExecutorFeePaidEvent.fc"; +#include "../../msglibs/ultralightnode/msgdata/UlnEvents.fc"; +#include "../../msglibs/ultralightnode/msgdata/UlnWorkerFeelibInfo.fc"; +#include "../../msglibs/ultralightnode/msgdata/UlnSend.fc"; +#include "../../msglibs/ultralightnode/msgdata/VerificationStatus.fc"; +#include "../../msglibs/ultralightnode/msgdata/UlnWorkerFeelibEvents.fc"; +#include "../../channel/storage.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "controller"; } + +cell createContractStorage() impure { + setContractStorage( + Controller::New( + getCaller(), + SRC_EID, + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE() + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(md::InitEndpoint::New(MOCK_CHANNEL_CODE())); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) deployChannel::success::basic(cell $storage) impure { + int owner = getContractAddress(); + + ;; corresponds to the path, as it indicates the 'local oApp' + spoofCaller(SRC_OAPP); + + ;; generate the endpoint address + cell endpointCode = $storage.cl::get(Controller::endpointCode); + cell $endpointInitStorage = Endpoint::New(SRC_EID, DST_EID, owner); + int endpointAddress = computeContractAddress($endpointInitStorage, endpointCode); + + cell channelCode = $storage.cl::get(Controller::channelCode); + + return test::handler::shouldPass( + deployChannel, + md::Deploy::New(INITIAL_DEPOSIT, DST_EID, DST_OAPP), + unsafeTuple([ + 0, + _newAction( + channelCode, + Channel::New(owner, MOCK_SEND_PATH(), endpointAddress), + INITIAL_DEPOSIT, + BaseInterface::OP::INITIALIZE, + cl::nullObject(), + 0 + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) deployChannel::revert::invalidEid(cell $storage) impure { + return test::handler::shouldFail( + deployChannel, + md::Deploy::New(INITIAL_DEPOSIT, 0, 1), + Controller::ERROR::invalidEid + ); +} + +(int, slice) deployEndpoint::success::basic(cell $storage) impure { + int owner = getContractAddress(); + + ;; pull or generate the expected endpoint deployed code + cell endpointCode = $storage.cl::get(Controller::endpointCode); + cell $endpointInitStorage = Endpoint::New(SRC_EID, DST_EID, owner); + cell channelCode = $storage.cl::get(Controller::channelCode); + + return test::handler::shouldPass( + deployEndpoint, + md::Deploy::New(INITIAL_DEPOSIT, DST_EID, NULLADDRESS), + unsafeTuple([ + 0, + _newAction( + endpointCode, + $endpointInitStorage, + INITIAL_DEPOSIT, + BaseInterface::OP::INITIALIZE, + md::InitEndpoint::New(channelCode), + 0 + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) deployEndpoint::revert::invalidEid(cell $storage) impure { + return test::handler::shouldFail( + deployEndpoint, + md::Deploy::New(INITIAL_DEPOSIT, 0, NULLADDRESS), + Controller::ERROR::invalidEid + ); +} + +(int, slice) setEpConfigDefaults::success::basic(cell $storage) impure { + ;; generate the md + cell $setEpConfigDefaultsMd = MOCK_SET_EP_CONFIG_MD(true); + + cell $mdEid = md::MdEid::New( + $setEpConfigDefaultsMd, + DST_EID + ); + + return test::handler::shouldPass( + setEpConfigDefaults, + $mdEid, + unsafeTuple([ + 0, + _newAction( + _calculateEndpointAddress(DST_EID), + Endpoint::OP::SET_EP_CONFIG_DEFAULTS, + $setEpConfigDefaultsMd + ) + ]), + $storage, ;; setEpConfigDefaults does not modify controller state + txnContext + ); +} + +(int, slice) setEpConfigDefaults::revert::invalidEid(cell $storage) impure { + return test::handler::shouldFail( + setEpConfigDefaults, + md::MdEid::New(MOCK_SET_EP_CONFIG_MD(true), 0), + Controller::ERROR::invalidEid + ); +} + +(int, slice) setEpConfigOApp::success::basic(cell $storage) impure { + int owner = getContractAddress(); + cell $path = MOCK_SEND_PATH(); + + ;; generate the md + cell $setEpConfigMd = MOCK_SET_EP_CONFIG_MD(true); + cell $mdObj = md::MdObj::New($setEpConfigMd, $path); + + ;; generate the endpoint address + cell $endpointInitData = Endpoint::New(SRC_EID, DST_EID, owner); + cell endpointCode = $storage.cl::get(Controller::endpointCode); + int endpointAddress = computeContractAddress($endpointInitData, endpointCode); + + ;; generate the channel address + cell $channelInitData = Channel::New(getContractAddress(), $path, endpointAddress); + cell channelCode = $storage.cl::get(Controller::channelCode); + int channelAddress = computeContractAddress($channelInitData, channelCode); + + return test::handler::shouldPass( + setEpConfigOApp, + $mdObj, + unsafeTuple([ + 0, + _newAction( + endpointAddress, + Endpoint::OP::SET_EP_CONFIG_OAPP, + $mdObj + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) setEpConfigOApp::revert::invalidEid(cell $storage) impure { + cell $path = MOCK_SEND_PATH().cl::set(lz::Path::dstEid, 0); + + return test::handler::shouldFail( + setEpConfigOApp, + md::MdObj::New(lz::EpConfig::NewWithDefaults(), $path), + Controller::ERROR::invalidEid + ); +} + +(int, slice) addMsglib::success::basic(cell $storage) impure { + cell $mdAddMsgLib = md::AddMsglib::New(MSGLIB_MANAGER_ADDRESS, DST_EID); + + setContractStorage( + $storage + .cl::set(Controller::eid, SRC_EID) + .cl::set(Controller::endpointCode, MOCK_ENDPOINT_CODE()) + ); + + cell $endpointInitData = Endpoint::New( + SRC_EID, + DST_EID, + getContractAddress() + ); + + int expectedEndpointAddress = computeContractAddress( + $endpointInitData, + MOCK_ENDPOINT_CODE() + ); + + return test::handler::shouldPass( + addMsglib, + $mdAddMsgLib, + unsafeTuple([ + 0, + _newAction( + expectedEndpointAddress, + Endpoint::OP::ADD_MSGLIB, + $mdAddMsgLib + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) addMsglib::revert::invalidEid(cell $storage) impure { + return test::handler::shouldFail( + addMsglib, + md::AddMsglib::New(MSGLIB_MANAGER_ADDRESS, 0), + Controller::ERROR::invalidEid + ); +} + +(int, slice) CoinsAmount::success::basic(cell $storage) impure { + tuple expectedTxnContext = txnContext.tset(_DONATION_NANOS, 0); + setDonationNanos(ZRO_FEE); + + int expectedEndpointAddress = _calculateEndpointAddress(DST_EID); + + int expectedChannelAddress = computeContractAddress( + Channel::New(getContractAddress(), MOCK_SEND_PATH(), expectedEndpointAddress), + $storage.cl::get(Controller::channelCode) + ); + + return test::handler::shouldPass( + depositZro, + MOCK_SEND_PATH(), + unsafeTuple([ + 0, + _newAction( + expectedChannelAddress, + Channel::OP::DEPOSIT_ZRO, + md::CoinsAmount::New(ZRO_FEE) + ) + ]), + getContractStorage(), + expectedTxnContext + ); +} + +(int, slice) setZroWallet::success::basic(cell $storage) impure { + cell $setAddress = md::SetAddress::New(PROTOCOL_ADMIN_ZRO_WALLET_ADDRESS); + + return test::handler::shouldPass( + setZroWallet, + $setAddress, + unsafeTuple([ + 0, + _newAction( + Controller::event::ZRO_WALLET_SET, + $setAddress + ) + ]), + getContractStorage().cl::set(Controller::zroWallet, PROTOCOL_ADMIN_ZRO_WALLET_ADDRESS), + txnContext + ); +} + +(int, slice) transferOwnership::success::basic(cell $storage) impure { + cell $setAddress = md::SetAddress::New(NEW_CONTROLLER_OWNER_ADDRESS); + + return test::handler::shouldPass( + transferOwnership, + $setAddress, + unsafeTuple([ + 0, + _newAction( + Controller::event::OWNER_SET_TENTATIVE, + $setAddress + ) + ]), + getContractStorage().cl::set(Controller::tentativeOwner, NEW_CONTROLLER_OWNER_ADDRESS), + txnContext + ); +} + +(int, slice) claimOwnership::success::basic(cell $storage) impure { + cell $setAddress = md::SetAddress::New(NEW_CONTROLLER_OWNER_ADDRESS); + + transferOwnership($setAddress); + + return test::handler::shouldPass( + claimOwnership, + cl::nullObject(), + unsafeTuple([ + 0, + _newAction( + Controller::event::OWNER_SET, + $setAddress + ) + ]), + getContractStorage() + .cl::set(Controller::tentativeOwner, NULLADDRESS) + .cl::set( + Controller::baseStorage, + $storage + .cl::get(Controller::baseStorage) + .cl::set(BaseStorage::owner, NEW_CONTROLLER_OWNER_ADDRESS) + ), + txnContext + ); +} + +(int, slice) claimOwnership::revert::nullTentativeOwner(cell $storage) impure { + return test::handler::shouldFail( + claimOwnership, + cl::nullObject(), + Controller::ERROR::nullTentativeOwner + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- deploy channel + .tpush([deployChannel::success::basic, "deployChannel::success::basic"]) + .tpush([deployChannel::revert::invalidEid, "deployChannel::revert::invalidEid"]) + ;; -- deploy endpoint + .tpush([deployEndpoint::success::basic, "deployEndpoint::success::basic"]) + .tpush([deployEndpoint::revert::invalidEid, "deployEndpoint::revert::invalidEid"]) + ;; -- set ep config defaults + .tpush([setEpConfigDefaults::success::basic, "setEpConfigDefaults::success::basic"]) + .tpush([setEpConfigDefaults::revert::invalidEid, "setEpConfigDefaults::revert::invalidEid"]) + ;; -- set ep config oapp + .tpush([setEpConfigOApp::success::basic, "setEpConfigOApp::success::basic"]) + .tpush([setEpConfigOApp::revert::invalidEid, "setEpConfigOApp::revert::invalidEid"]) + ;; -- add msg lib + .tpush([addMsglib::success::basic, "addMsglib::success::basic"]) + .tpush([addMsglib::revert::invalidEid, "addMsglib::revert::invalidEid"]) + ;; -- deposit zro + .tpush([CoinsAmount::success::basic, "CoinsAmount::success::basic"]) + ;; -- set zro wallet + .tpush([setZroWallet::success::basic, "setZroWallet::success::basic"]) + ;; -- transfer ownership + .tpush([transferOwnership::success::basic, "transferOwnership::success::basic"]) + ;; -- claim ownership + .tpush([claimOwnership::success::basic, "claimOwnership::success::basic"]) + .tpush([claimOwnership::revert::nullTentativeOwner, "claimOwnership::revert::nullTentativeOwner"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/permissions.fc new file mode 100644 index 00000000..57e9e4d5 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/controller/tests/permissions.fc @@ -0,0 +1,172 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/testMain.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Controller::permissions"; } + +cell createContractStorage() impure { + setContractStorage( + Controller::New( + getCaller(), + SRC_EID, + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE() + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(md::InitEndpoint::New(MOCK_CHANNEL_CODE())); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) checkPermissions::deployEndpoint::success::basic(cell $storage) impure { + return test::permissions::shouldPass(Controller::OP::DEPLOY_ENDPOINT, cl::nullObject()); +} + +(int, slice) checkPermissions::deployEndpoint::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Controller::OP::DEPLOY_ENDPOINT, cl::nullObject()); +} + +(int, slice) checkPermissions::deployChannel::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass(Controller::OP::DEPLOY_CHANNEL, cl::nullObject()); +} + +(int, slice) checkPermissions::setEpConfigDefaults::success::basic(cell $storage) impure { + return test::permissions::shouldPass(Controller::OP::SET_EP_CONFIG_DEFAULTS, cl::nullObject()); +} + +(int, slice) checkPermissions::setEpConfigDefaults::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Controller::OP::SET_EP_CONFIG_DEFAULTS, cl::nullObject()); +} + +(int, slice) checkPermissions::setEpConfigOApp::success::basic(cell $storage) impure { + spoofCaller(SRC_OAPP); + return test::permissions::shouldPass( + Controller::OP::SET_EP_CONFIG_OAPP, + md::ExtendedMd::New( + cl::nullObject(), + MOCK_SEND_PATH(), + NULLADDRESS + ) + ); +} + +(int, slice) checkPermissions::setEpConfigOApp::revert::notOApp(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Controller::OP::SET_EP_CONFIG_OAPP, cl::nullObject()); +} + +(int, slice) checkPermissions::addMsglib::success::basic(cell $storage) impure { + return test::permissions::shouldPass(Controller::OP::ADD_MSGLIB, cl::nullObject()); +} + +(int, slice) checkPermissions::addMsglib::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Controller::OP::ADD_MSGLIB, cl::nullObject()); +} + +(int, slice) checkPermissions::CoinsAmount::success::basic(cell $storage) impure { + spoofCaller(_getZroWalletAddress()); + return test::permissions::shouldPass(Controller::OP::EXCESSES, cl::nullObject()); +} + +(int, slice) checkPermissions::CoinsAmount::revert::notWallet(cell $storage) impure { + return test::permissions::shouldFail(Controller::OP::DEPOSIT_ZRO, cl::nullObject()); +} + +(int, slice) checkPermissions::setZroWallet::success::basic(cell $storage) impure { + return test::permissions::shouldPass(Controller::OP::SET_ZRO_WALLET, cl::nullObject()); +} + +(int, slice) checkPermissions::setZroWallet::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Controller::OP::SET_ZRO_WALLET, cl::nullObject()); +} + +(int, slice) checkPermissions::transferOwnership::success::basic(cell $storage) impure { + return test::permissions::shouldPass( + Controller::OP::TRANSFER_OWNERSHIP, + md::SetAddress::New(NEW_CONTROLLER_OWNER_ADDRESS) + ); +} + +(int, slice) checkPermissions::transferOwnership::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Controller::OP::TRANSFER_OWNERSHIP, cl::nullObject()); +} + +(int, slice) checkPermissions::claimOwnership::success::basic(cell $storage) impure { + transferOwnership(md::SetAddress::New(NEW_CONTROLLER_OWNER_ADDRESS)); + spoofCaller(NEW_CONTROLLER_OWNER_ADDRESS); + return test::permissions::shouldPass( + Controller::OP::CLAIM_OWNERSHIP, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimOwnership::revert::notTentativeOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Controller::OP::CLAIM_OWNERSHIP, cl::nullObject()); +} + +(int, slice) checkPermissions::default::revert::invalidOpCode(cell $storage) impure { + ;; this is a generic opcode that we should NOT allow + return test::permissions::shouldFail(OP::RANDOM, cl::nullObject()); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- deploy endpoint + .tpush([checkPermissions::deployEndpoint::success::basic, "checkPermissions::deployEndpoint::success::basic"]) + .tpush([checkPermissions::deployEndpoint::revert::notOwner, "checkPermissions::deployEndpoint::revert::notOwner"]) + ;; -- deploy channel + .tpush([checkPermissions::deployChannel::success::basic, "checkPermissions::deployChannel::success::basic"]) + ;; -- set ep config defaults + .tpush([checkPermissions::setEpConfigDefaults::success::basic, "checkPermissions::setEpConfigDefaults::success::basic"]) + .tpush([checkPermissions::setEpConfigDefaults::revert::notOwner, "checkPermissions::setEpConfigDefaults::revert::notOwner"]) + ;; -- set ep config oapp + .tpush([checkPermissions::setEpConfigOApp::success::basic, "checkPermissions::setEpConfigOApp::success::basic"]) + .tpush([checkPermissions::setEpConfigOApp::revert::notOApp, "checkPermissions::setEpConfigOApp::revert::notOApp"]) + ;; -- add msglib + .tpush([checkPermissions::addMsglib::success::basic, "checkPermissions::addMsglib::success::basic"]) + .tpush([checkPermissions::addMsglib::revert::notOwner, "checkPermissions::addMsglib::revert::notOwner"]) + ;; -- deposit zro + .tpush([checkPermissions::CoinsAmount::success::basic, "checkPermissions::CoinsAmount::success::basic"]) + .tpush([checkPermissions::CoinsAmount::revert::notWallet, "checkPermissions::CoinsAmount::revert::notWallet"]) + ;; -- set zro wallet + .tpush([checkPermissions::setZroWallet::success::basic, "checkPermissions::setZroWallet::success::basic"]) + .tpush([checkPermissions::setZroWallet::revert::notOwner, "checkPermissions::setZroWallet::revert::notOwner"]) + ;; -- transfer ownership + .tpush([checkPermissions::transferOwnership::success::basic, "checkPermissions::transferOwnership::success::basic"]) + .tpush([checkPermissions::transferOwnership::revert::notOwner, "checkPermissions::transferOwnership::revert::notOwner"]) + ;; -- claim ownership + .tpush([checkPermissions::claimOwnership::success::basic, "checkPermissions::claimOwnership::success::basic"]) + .tpush([checkPermissions::claimOwnership::revert::notTentativeOwner, "checkPermissions::claimOwnership::revert::notTentativeOwner"]) + .tpush([checkPermissions::default::revert::invalidOpCode, "checkPermissions::default::revert::invalidOpCode"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/abstract/protocolHandler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/abstract/protocolHandler.fc new file mode 100644 index 00000000..0293a381 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/abstract/protocolHandler.fc @@ -0,0 +1,100 @@ +#include "../../../funC++/handlerCore.fc"; +#include "../../../funC++/actions/call.fc"; +#include "../../../funC++/actions/deploy.fc"; +#include "../../../funC++/actions/event.fc"; +#include "../../../funC++/actions/payment.fc"; + +#include "../baseStorage.fc"; + +int getOwner() impure inline { + return getBaseStorage().BaseStorage::getOwner(); +} + +cell getInitialStorage() impure inline { + return getBaseStorage().BaseStorage::getInitialStorage(); +} + +;;; ========================================== +;; Modifiers +() assertAuthenticated() impure inline { + throw_unless( + BaseInterface::ERROR::notAuthenticated, + getBaseStorage().BaseStorage::getAuthenticated() + ); +} + +() assertInitialized() impure inline { + throw_unless( + BaseInterface::ERROR::notInitialized, + getBaseStorage().BaseStorage::getInitialized() + ); +} + +;; assert the ctx sender is the owner of this contract +;; expects the ctx to be populated. Does not require storage to be loaded +() assertOwner() impure inline { + throw_unless( + BaseInterface::ERROR::onlyOwner, + getCaller() == getOwner() + ); +} + +;; Step 1: authenticate +() authenticate() impure { + assertOwner(); + throw_if( + BaseInterface::ERROR::alreadyInitialized, + getBaseStorage().BaseStorage::getInitialized() + ); + cell $storage = getContractStorage(); + + setContractStorage( + $storage + .cl::set( + BASE_STORAGE_INDEX, + $storage + .cl::get(BASE_STORAGE_INDEX) + .cl::set( + BaseStorage::initialStorage, + getContractStorage() + ) + .cl::set(BaseStorage::authenticated, true) + ) + ); +} + +() authenticateIfNecessary() impure inline { + if (getBaseStorage().BaseStorage::getAuthenticated() == false) { + authenticate(); + } +} + +(cell, tuple) _initialize(cell $md) impure inline; + +;; Step 2: initialize +tuple initialize(cell $md) impure inline { + assertAuthenticated(); + if (getBaseStorage().BaseStorage::getInitialized()) { + return emptyActions(); + } + + (cell $storage, tuple actions) = _initialize($md); + + setContractStorage( + $storage + .cl::set( + BASE_STORAGE_INDEX, + $storage + .cl::get(BASE_STORAGE_INDEX) + .cl::set(BaseStorage::initialized, true) + ) + ); + + return actions; +} + +;; declared inside of the actions/event.fc +;; We declare it here because it saves the need for declaring initialStorage everytime we call event +tuple _newAction(int topic, cell $body) impure inline { + return action::event::create(topic, $body, getInitialStorage()); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/abstract/protocolMain.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/abstract/protocolMain.fc new file mode 100644 index 00000000..aa35cf5c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/abstract/protocolMain.fc @@ -0,0 +1,30 @@ +;;; ================================================================ +;; The base main function for LayerZero Endpoint, UltraLightNode, and OApp +;;; ================================================================ +#include "../../../funC++/actions/call.fc"; +#include "../../../funC++/actions/deploy.fc"; +#include "../../../funC++/actions/dispatch.fc"; +#include "../../../funC++/actions/event.fc"; +#include "../../../funC++/actions/payment.fc"; + +#include "../../../funC++/contractMain.fc"; +#include "../../../funC++/handlerCore.fc"; + +int _executeAction(int actionType, tuple action) impure inline { + if (actionType == action::event::NAME) { + return executeEvent(action); + } elseif (actionType == action::call::NAME) { + return executeCall(action); + } elseif (actionType == action::payment::NAME) { + return executePayment(action); + } elseif (actionType == action::dispatch::NAME) { + return executeDispatch(action); + } elseif (actionType == action::deploy::NAME) { + return executeDeploy(action); + } else { + throw(BaseInterface::ERROR::invalidActionType); + } + + ;; compiler freaks out if you dont have something here returning an int, but this should never be reached + return false; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/baseStorage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/baseStorage.fc new file mode 100644 index 00000000..8821a546 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/core/baseStorage.fc @@ -0,0 +1,57 @@ +#include "../../funC++/classlib.fc"; +#include "../../funC++/baseInterface.fc"; + +;; !!! If you put this storage anywhere other than index 0 of your custom contract storage, +;; you are gunna have a bad time +const int BASE_STORAGE_INDEX = 0; + +;; required object name +const int BaseStorage::NAME = "baseStore"u; + +;; field names +const int BaseStorage::owner = 0; +const int BaseStorage::authenticated = 1; +const int BaseStorage::initialized = 2; +const int BaseStorage::initialStorage = 3; + +;; In all blockchains with atomic cross-contract call, we can use src/dst/sender/receiver +;; because the send channel doesn't exist (it's just a nonce). +;; In TON, we need both send/receive channels, so we use local/remote to provide +;; a context-free way to refer to the two ends of the channel. +;; The direction is inferred by the context of the contract (send vs receive). +;; The srcOApp is the 256-bit hashpart of a standard address. +cell BaseStorage::New(int owner) impure inline method_id { + return cl::declare( + BaseStorage::NAME, + unsafeTuple([ + [cl::t::address, owner], ;; BaseStorage::owner + [cl::t::bool, false], ;; BaseStorage::authenticated + [cl::t::bool, false], ;; BaseStorage::initialized + [cl::t::objRef, cl::nullObject()] ;; BaseStorage::initialStorage + ]) + ); +} + +const int BaseStorage::_ownerOffset = _HEADER_WIDTH; +const int BaseStorage::_authenticatedOffset = BaseStorage::_ownerOffset + 256; +const int BaseStorage::_initializedOffset = BaseStorage::_authenticatedOffset + 1; + +int BaseStorage::getOwner(cell $self) impure inline { + return $self.cellPreloadAddressAt(BaseStorage::_ownerOffset); +} + +int BaseStorage::getAuthenticated(cell $self) impure inline { + return $self.cellPreloadBoolAt(BaseStorage::_authenticatedOffset); +} + +int BaseStorage::getInitialized(cell $self) impure inline { + return $self.cellPreloadBoolAt(BaseStorage::_initializedOffset); +} + +cell BaseStorage::getInitialStorage(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +cell getBaseStorage() impure inline method_id { + return getContractStorage().cellPreloadRefAt(BASE_STORAGE_INDEX); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/handler.fc new file mode 100644 index 00000000..a77ffc17 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/handler.fc @@ -0,0 +1,484 @@ +#include "../core/abstract/protocolHandler.fc"; + +#include "../../classes/lz/SendEpConfig.fc"; +#include "../../classes/lz/MsglibInfo.fc"; +#include "../../classes/lz/ReceiveEpConfig.fc"; +#include "../../classes/lz/Packet.fc"; +#include "../../classes/lz/Path.fc"; +#include "../../classes/lz/EpConfig.fc"; + +#include "../../classes/msgdata/ExtendedMd.fc"; +#include "../../classes/msgdata/MdAddress.fc"; +#include "../../classes/msgdata/MdObj.fc"; +#include "../../classes/msgdata/AddMsglib.fc"; +#include "../../classes/msgdata/InitEndpoint.fc"; +#include "../../classes/msgdata/LzSend.fc"; +#include "../../classes/msgdata/SetEpConfig.fc"; + +#include "../../funC++/txnContext.fc"; + +#include "../channel/storage.fc"; +#include "../interfaces.fc"; +#include "interface.fc"; +#include "storage.fc"; + +;;; ==========================HELPER FUNCTIONS===================================== + +;; In LayerZero-TON, the msglib manager address is the outward facing alias for the messagelib +;; that all users and admins specify in configurations etc. +;; Within the protocol scope, the channel directly communicates `to` the Messagelib Connection +;; shard and receives communication `from` some other arbitrary messagelib shard (for efficiency). +;; shard addresses, this is where the validity of the messagelib is checked (i.e., that no +;; unauthorized messagelib can be chosen in a configuration). +;; @return (msglibAddress: address, msglibConnectionAddress: address) +(int, int) _msglibManagerToShards(int msglibManagerAddress, cell $path) impure inline method_id { + if (msglibManagerAddress == NULLADDRESS) { + return (NULLADDRESS, NULLADDRESS); + } + + (cell $msglibInfo, int exists) = getContractStorage() + .cl::nestedDict256::get(Endpoint::msglibs, msglibManagerAddress); + + throw_unless( + Endpoint::ERROR::unauthorizedMsglib, + exists + ); + + throw_if( + Endpoint::ERROR::unresolvedMsglib, + cl::isNullObject($msglibInfo) + ); + + return ( + $msglibInfo.cl::get
(lz::MsglibInfo::msglibAddress), + lz::MsglibInfo::getMsglibConnectionAddress($msglibInfo, $path) + ); +} + +;; Derive the channel address for a given path +int _getChannelAddress(cell $storage, cell $path) impure inline method_id { + return computeContractAddress( + $storage + .Endpoint::getChannelStorageInit() + .Channel::setPath($path), + $storage.Endpoint::getChannelCode() + ); +} + +int _getChannelAddressUnpacked(cell $channelStorage, cell channelCode, cell $path) impure inline { + return computeContractAddress( + $channelStorage.Channel::setPath($path), + channelCode + ); +} + +;; resolve manager addresses into msglib addresses +;; throws if the msglib manager is not registered or resolved +cell _getEpConfigFromManagerAddresses(cell $setEpConfigMd, cell $path) impure inline method_id { + int sendMsglibManager = $setEpConfigMd.cl::get
(md::SetEpConfig::sendMsglibManager); + (int sendMsglib, int sendMsglibConnection) = _msglibManagerToShards( + sendMsglibManager, + $path + ); + + int receiveMsglibManager = $setEpConfigMd.cl::get
(md::SetEpConfig::receiveMsglibManager); + (int receiveMsglib, int receiveMsglibConnection) = _msglibManagerToShards( + receiveMsglibManager, + $path + ); + + int timeoutReceiveMsglibManager = $setEpConfigMd.cl::get
(md::SetEpConfig::timeoutReceiveMsglibManager); + (int timeoutReceiveMsglib, int timeoutReceiveMsglibConnection) = _msglibManagerToShards( + timeoutReceiveMsglibManager, + $path + ); + + throw_if( + Endpoint::ERROR::sameTimeoutAndReceive, + (receiveMsglibManager == timeoutReceiveMsglibManager) & (receiveMsglibManager != NULLADDRESS) + ); + + return lz::EpConfig::NewWithConnection( + $setEpConfigMd.cl::get(md::SetEpConfig::useDefaults), + sendMsglibManager, + sendMsglib, + sendMsglibConnection, + receiveMsglib, + receiveMsglibConnection, + timeoutReceiveMsglib, + timeoutReceiveMsglibConnection, + $setEpConfigMd.cl::get(md::SetEpConfig::timeoutReceiveMsglibExpiry) + ); +} + +(int, int) _getMsglibAndConnectionDefaults(cell $msglibInfo, cell $path) impure inline method_id { + if (cl::isNullObject($msglibInfo)) { + return (NULLADDRESS, NULLADDRESS); + } + + return ( + lz::MsglibInfo::getMsglibAddress($msglibInfo), + lz::MsglibInfo::getMsglibConnectionAddress($msglibInfo, $path) + ); +} + +(int, int) getSendMsglibAndConnectionDefaults(cell $storage, cell $path) impure inline method_id { + return _getMsglibAndConnectionDefaults( + $storage.Endpoint::getDefaultSendLibInfo(), + $path + ); +} + +(int, int) getReceiveMsglibAndConnectionDefaults(cell $storage, cell $path) inline method_id { + return _getMsglibAndConnectionDefaults( + $storage.Endpoint::getDefaultReceiveLibInfo(), + $path + ); +} + +(int, int) getTimeoutReceiveMsglibAndConnectionDefaults(cell $storage, cell $path) impure inline method_id { + return _getMsglibAndConnectionDefaults( + $storage.Endpoint::getDefaultTimeoutReceiveLibInfo(), + $path + ); +} + +() _assertAuthorizedMsglib() impure inline method_id { + (_, int exists) = getContractStorage() + .Endpoint::getMsglibs() + .cl::dict256::get(getCaller()); + + throw_unless(Endpoint::ERROR::unauthorizedMsglib, exists); +} + +;;; ================INTERFACE FUNCTIONS===================== + +(cell, tuple) _initialize(cell $initEndpoint) impure inline { + (cell $storage, tuple actions) = preamble(); + return ( + $storage + .cl::set( + Endpoint::channelCode, + $initEndpoint.cl::get(md::InitEndpoint::channelCode) + ) + .cl::set( + Endpoint::channelStorageInit, + Channel::New( + getOwner(), + lz::Path::endpointPath( + $storage.cl::get(Endpoint::eid), + $storage.cl::get(Endpoint::dstEid) + ), + getContractAddress() + ) + ), + actions + ); +} + +int _getEventSink() impure inline { + return getContractStorage() + .Endpoint::getBaseStorage() + .BaseStorage::getOwner(); +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + if (op == Endpoint::OP::ENDPOINT_SEND) { + throw_unless(Endpoint::ERROR::notOApp, + getCaller() == $md + .md::LzSend::getPath() + .lz::Path::getSrcOApp() + ); + } elseif (op == Endpoint::OP::ENDPOINT_COMMIT_PACKET) { + ;; open and public calls + return (); + } elseif ( + (op == Endpoint::OP::SET_EP_CONFIG_OAPP) + | (op == Endpoint::OP::SET_EP_CONFIG_DEFAULTS) + | (op == Endpoint::OP::ADD_MSGLIB) + ) { + return assertOwner(); ;; controller + } elseif (op == Endpoint::OP::GET_MSGLIB_INFO_CALLBACK) { + return _assertAuthorizedMsglib(); + } else { + ;; we must put a check for all opcodes to make sure we don't + ;; mistakenly miss an opp code's permissions + throw(BaseInterface::ERROR::invalidOpcode); + } +} + +;;; ==========================HANDLERS===================================== +;; @in controller/handler.fc/setEpConfigDefaults +;; @in_md setEpConfig +;; @out controller/handler.fc/emit_event +;; @permissions owner +;; We MIGHT not support defaults for certain connections/dstEids +tuple setEpConfigDefaults(cell $setEpConfigMd) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedSetEpConfig = $setEpConfigMd.md::SetEpConfig::sanitize(); + + cell $path = lz::Path::endpointPath( + $storage.cl::get(Endpoint::eid), + $storage.cl::get(Endpoint::dstEid) + ); + + int sendLibManager = $sanitizedSetEpConfig + .cl::get
(md::SetEpConfig::sendMsglibManager); + + int receiveLibManager = $sanitizedSetEpConfig + .cl::get
(md::SetEpConfig::receiveMsglibManager); + + int timeoutReceiveLibManager = $sanitizedSetEpConfig + .cl::get
(md::SetEpConfig::timeoutReceiveMsglibManager); + + (cell $sendLibInfo, int sendLibExists) = getContractStorage() + .cl::nestedDict256::get(Endpoint::msglibs, sendLibManager); + + (cell $receiveLibInfo, int receiveLibExists) = getContractStorage() + .cl::nestedDict256::get(Endpoint::msglibs, receiveLibManager); + + (cell $timeoutReceiveLibInfo, int timeoutReceiveLibExists) = getContractStorage() + .cl::nestedDict256::get(Endpoint::msglibs, timeoutReceiveLibManager); + + int expiry = $sanitizedSetEpConfig.cl::get(md::SetEpConfig::timeoutReceiveMsglibExpiry); + throw_unless( + Endpoint::ERROR::unauthorizedMsglib, + (sendLibExists | (sendLibManager == NULLADDRESS)) + & (receiveLibExists | (receiveLibManager == NULLADDRESS)) + & (timeoutReceiveLibExists | (timeoutReceiveLibManager == NULLADDRESS)) + ); + throw_if( + Endpoint::ERROR::sameTimeoutAndReceive, + (timeoutReceiveLibManager != NULLADDRESS) & (timeoutReceiveLibManager == receiveLibManager) + ); + throw_unless( + Endpoint::ERROR::invalidExpiry, + ((timeoutReceiveLibManager == NULLADDRESS) & (expiry == 0)) + | ((timeoutReceiveLibManager != NULLADDRESS) & (expiry > now())) + ); + + ;; make sure the manager addresses can be resolved + int validity = _getEpConfigFromManagerAddresses($sanitizedSetEpConfig, $path).lz::EpConfig::isValid(); + ;; The above block validates *all* the inputs, meaning this throw_if will actually never + ;; throw. We leave it in because it doesn't hurt, might as well keep it. + ;; The extra gas cost is inconsequential because this is a configuration flow. + throw_if(validity, validity != lz::EpConfig::VALID); + + if ($sendLibInfo.is_null()) { + $sendLibInfo = cl::nullObject(); + } + if ($receiveLibInfo.is_null()) { + $receiveLibInfo = cl::nullObject(); + } + if ($timeoutReceiveLibInfo.is_null()) { + $timeoutReceiveLibInfo = cl::nullObject(); + } + + setContractStorage( + $storage + .cl::set(Endpoint::defaultSendMsglibManager, sendLibManager) + .cl::set(Endpoint::defaultSendLibInfo, $sendLibInfo) + .cl::set(Endpoint::defaultReceiveLibInfo, $receiveLibInfo) + .cl::set(Endpoint::defaultTimeoutReceiveLibInfo, $timeoutReceiveLibInfo) + .cl::set(Endpoint::defaultExpiry, expiry) + ); + + ;; push the event + actions~pushAction(Endpoint::event::EP_CONFIG_DEFAULTS_SET, $sanitizedSetEpConfig); + + return actions; +} + +;; @in controller/handler.fc/setEpConfig +;; @in_md ExtendedMd(channelAddress, setEpConfig) +;; @out channel/handler.fc/setEpConfig +;; @out_md ExtendedMd(defaultEpConfig, setEpConfig) +;; @permissions owner +tuple setEpConfigOApp(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + ;; extract the md params + cell $sanitizedSetEpConfigOApp = $mdObj.cl::get(md::MdObj::md).md::SetEpConfig::sanitize(); + cell $sanitizedPath = $mdObj.cl::get(md::MdObj::obj).lz::Path::sanitize(); + cell $resolvedEpConfigOApp = _getEpConfigFromManagerAddresses($sanitizedSetEpConfigOApp, $sanitizedPath); + + ;; send message to channel and forward the ep config defaults + actions~pushAction( + _getChannelAddress($storage, $sanitizedPath), + Channel::OP::SET_EP_CONFIG_OAPP, + $resolvedEpConfigOApp + ); + + return actions; +} + +;; @in oApp/handler.fc/send +;; @in_md lzSend +;; @out channel/handler.fc/send +;; @out_md MdObj(lzSend, defaultSendConfig) +;; @permissions OApp +tuple endpointSend(cell $lzSend) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + ;; already asserted caller identity matches the request contents in checkPermissions() + cell $path = $lzSend.md::LzSend::getPath(); + + ( + cell $channelStorageInit, + cell channelCode, + int defaultSendMsglibManager, + cell $defaultSendLibInfo + ) = $storage.Endpoint::getSendConfiguration(); + + ;; Invariant: if a msglib is set as a default msglib, it must be resolved + ;; thus, getSendMsglibAndConnectionDefaults should never revert + ( + int sendMsglib, + int sendMsglibConnection + ) = _getMsglibAndConnectionDefaults($defaultSendLibInfo, $path); + + actions~pushAction( + _getChannelAddressUnpacked($channelStorageInit, channelCode, $path), + Channel::OP::CHANNEL_SEND, + md::MdObj::build( + $lzSend, + lz::SendEpConfig::build(defaultSendMsglibManager, sendMsglib, sendMsglibConnection) + ) + ); + + return actions; +} + +;; @in msglib/handler.fc/commitVerification +;; @in_md ExtendedMd(msglib_addr, _, endpointCommitPacket) +;; @out channel/handler.fc/endpointCommitPacket +;; @out_md ExtendedMd(msglib_addr, defaultEpConfig, endpointCommitPacket) +tuple endpointCommitPacket(cell $packet) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $receivePath = $packet.lz::Packet::getPath(); + (int recvPathSrcEid, int recvPathDstEid) = $receivePath.lz::Path::getEidAndDstEid(); + ( + int storageEid, + int storageDstEid, + int defaultExpiry, + cell $defaultReceiveLibInfo, + cell $timeoutReceiveLibInfo + ) = $storage.Endpoint::getCommitPacketInformation(); + + ;; Assert we are the correct endpoint for this commit request + ;; the srcEid of the packet should match the dstEid of the endpoint contract + throw_if( + Endpoint::ERROR::wrongPath, + (recvPathSrcEid != storageDstEid) | (recvPathDstEid != storageEid) + ); + + cell $sendPath = $receivePath.lz::Path::optimizedReverse(); + int channelAddress = _getChannelAddress($storage, $sendPath); + + (_, int receiveMsglibConnection) = _getMsglibAndConnectionDefaults( + $defaultReceiveLibInfo, + $sendPath + ); + + (_, int timeoutReceiveMsglibConnection) = _getMsglibAndConnectionDefaults( + $timeoutReceiveLibInfo, + $sendPath + ); + + actions~pushAction( + channelAddress, + Channel::OP::CHANNEL_COMMIT_PACKET, + md::ExtendedMd::build( + $packet, + lz::ReceiveEpConfig::build( + receiveMsglibConnection, + timeoutReceiveMsglibConnection, + defaultExpiry + ), + getCaller() + ) + ); + + return actions; +} + +;; @permissions controller +;; @in controller/handler.fc/addMsglib +;; @in_md resolve_msglib (msglib manager address) +;; @out msglib controller/handler.fc/resolve_msglib +;; @out_md resolve_msglib (msglib manager address, eid) +tuple addMsglib(cell $addMsglib) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedAddMsglibMd = $addMsglib.md::AddMsglib::sanitize(); + + ;; Not necessary to check dstEid in $addMsglib is correct, because + ;; the controller derives our address based on dstEid, so it cannot be wrong. + + int msglibManagerAddress = $sanitizedAddMsglibMd.cl::get
(md::AddMsglib::msglibManagerAddress); + (cell $msglibInfo, int exists) = $storage.cl::nestedDict256::get( + Endpoint::msglibs, + msglibManagerAddress + ); + + ifnot (exists) { + ;; Check that we havent exceeded the max number of msglibs + int currentNumMsglibs = $storage.cl::get(Endpoint::numMsglibs); + throw_unless(Endpoint::ERROR::numMsglibsExceeded, currentNumMsglibs < MAX_MSGLIBS); + + setContractStorage( + $storage + .cl::nestedDict256::setRef( + Endpoint::msglibs, + msglibManagerAddress, + cl::nullObject() + ) + .cl::set(Endpoint::numMsglibs, currentNumMsglibs + 1) + ); + + ;; Call the msglib manager to resolve the correct msglib contract + ;; address for this endpoint's remote eid + actions~pushAction( + msglibManagerAddress, + MsglibManager::OP::GET_MSGLIB_INFO, + $sanitizedAddMsglibMd + ); + } else { + ;; if the previous addMsglib ran out of gas, it can be retried + if (cl::isNullObject($msglibInfo)) { + actions~pushAction( + msglibManagerAddress, + MsglibManager::OP::GET_MSGLIB_INFO, + $sanitizedAddMsglibMd + ); + } + } + + return actions; +} + +;; @in Any MsglibManager that was added in addMsglib +;; @in_md MsglibInfo +;; @out refund +tuple getMsglibInfoCallback(cell $msglibInfo) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedMsglibInfo = $msglibInfo.lz::MsglibInfo::sanitize(); + + ;; the storedMsglibInfo is asserted to exist in checkPermissions + (cell $storedMsglibInfo, _) = $storage.cl::nestedDict256::get( + Endpoint::msglibs, + getCaller() + ); + + throw_unless(Endpoint::ERROR::msglibInfoExists, cl::isNullObject($storedMsglibInfo)); + + setContractStorage( + $storage.cl::nestedDict256::setRef(Endpoint::msglibs, getCaller(), $sanitizedMsglibInfo) + ); + + return actions; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/interface.fc new file mode 100644 index 00000000..bf666a44 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/interface.fc @@ -0,0 +1,20 @@ +;; OPCODES +const int Endpoint::OP::ENDPOINT_SEND = "Endpoint::OP::ENDPOINT_SEND"c; +const int Endpoint::OP::ENDPOINT_COMMIT_PACKET = "Endpoint::OP::ENDPOINT_COMMIT_PACKET"c; +const int Endpoint::OP::SET_EP_CONFIG_DEFAULTS = "Endpoint::OP::SET_EP_CONFIG_DEFAULTS"c; +const int Endpoint::OP::SET_EP_CONFIG_OAPP = "Endpoint::OP::SET_EP_CONFIG_OAPP"c; +const int Endpoint::OP::ADD_MSGLIB = "Endpoint::OP::ADD_MSGLIB"c; +const int Endpoint::OP::GET_MSGLIB_INFO_CALLBACK = "Endpoint::OP::GET_MSGLIB_INFO_CALLBACK"c; + +;; EVENTS +const int Endpoint::event::EP_CONFIG_DEFAULTS_SET = "EP_CONFIG_DEFAULTS_SET"u; + +;; ERRORS +const int Endpoint::ERROR::notOApp = 300; +const int Endpoint::ERROR::wrongPath = 301; +const int Endpoint::ERROR::unauthorizedMsglib = 302; +const int Endpoint::ERROR::invalidExpiry = 303; +const int Endpoint::ERROR::unresolvedMsglib = 304; +const int Endpoint::ERROR::msglibInfoExists = 305; +const int Endpoint::ERROR::numMsglibsExceeded = 306; +const int Endpoint::ERROR::sameTimeoutAndReceive = 307; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/main.fc new file mode 100644 index 00000000..59e8bedb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/main.fc @@ -0,0 +1,23 @@ +#include "../core/abstract/protocolMain.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == Endpoint::OP::ENDPOINT_SEND) { + return endpointSend($md); + } elseif (op == Endpoint::OP::ENDPOINT_COMMIT_PACKET) { + return endpointCommitPacket($md); + } elseif (op == Endpoint::OP::SET_EP_CONFIG_DEFAULTS) { + return setEpConfigDefaults($md); + } elseif (op == Endpoint::OP::SET_EP_CONFIG_OAPP) { + return setEpConfigOApp($md); + } elseif (op == Endpoint::OP::ADD_MSGLIB) { + return addMsglib($md); + } elseif (op == Endpoint::OP::GET_MSGLIB_INFO_CALLBACK) { + return getMsglibInfoCallback($md); + } + throw(BaseInterface::ERROR::invalidOpcode); + return empty_tuple(); +} + \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/storage.fc new file mode 100644 index 00000000..9c4f0da1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/storage.fc @@ -0,0 +1,123 @@ +#include "../core/baseStorage.fc"; + +;; required object name +const int Endpoint::NAME = "endpoint"u; + +;; field names +const int Endpoint::baseStorage = 0; +const int Endpoint::eid = 1; +const int Endpoint::dstEid = 2; +const int Endpoint::msglibs = 3; +const int Endpoint::numMsglibs = 4; +const int Endpoint::channelCode = 5; +const int Endpoint::channelStorageInit = 6; +const int Endpoint::defaultSendMsglibManager = 7; +const int Endpoint::defaultSendLibInfo = 8; +const int Endpoint::defaultReceiveLibInfo = 9; +const int Endpoint::defaultTimeoutReceiveLibInfo = 10; +const int Endpoint::defaultExpiry = 11; + +;; @owner manager +cell Endpoint::New(int eid, int dstEid, int owner) impure inline method_id { + return cl::declare( + Endpoint::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; Endpoint::baseStorage + [cl::t::uint32, eid], ;; Endpoint::eid + [cl::t::uint32, dstEid], ;; Endpoint::dstEid + [cl::t::dict256, cl::dict256::New()], ;; Endpoint::msglibs + [cl::t::uint8, 0], ;; Endpoint::numMsglibs + [cl::t::cellRef, empty_cell()], ;; Endpoint::channelCode + [cl::t::objRef, cl::nullObject()], ;; Endpoint::channelStorageInit + [cl::t::address, NULLADDRESS], ;; Endpoint::defaultSendMsglibManager + [cl::t::objRef, cl::nullObject()], ;; Endpoint::defaultSendLibInfo + [cl::t::objRef, cl::nullObject()], ;; Endpoint::defaultReceiveLibInfo + [cl::t::objRef, cl::nullObject()], ;; Endpoint::defaultTimeoutReceiveLibInfo + [cl::t::uint64, 0] ;; Endpoint::defaultExpiry + ]) + ); +} + +;; ====================== Storage Accessors ===================== + +const int Endpoint::_eidOffset = _HEADER_WIDTH; +const int Endpoint::_dstEidOffset = Endpoint::_eidOffset + 32; +const int Endpoint::_numMsglibsOffset = Endpoint::_dstEidOffset + 32; +const int Endpoint::_defaultSendMsglibManagerOffset = Endpoint::_numMsglibsOffset + 8; +const int Endpoint::_defaultExpiryOffset = Endpoint::_defaultSendMsglibManagerOffset + 256; + +cell Endpoint::getBaseStorage(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +cell Endpoint::getMsglibs(cell $self) impure inline { + return $self.cellPreloadRefAt(1); +} + +cell Endpoint::getChannelCode(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(0); +} + +cell Endpoint::getChannelStorageInit(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(1); +} + +cell Endpoint::getDefaultSendLibInfo(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(2); +} + +cell Endpoint::getDefaultReceiveLibInfo(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(3); +} + +cell Endpoint::getDefaultTimeoutReceiveLibInfo(cell $self) impure inline { + return $self.cellPreloadRefAt(3).cellPreloadRefAt(0); +} + +;; ====================== Composite Storage Accessors ===================== + +;; (channelStorageInit, channelCode, defaultSendMsglibManager, defaultSendLibInfo) +(cell, cell, int, cell) Endpoint::getSendConfiguration(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2Slice = selfSlice.preloadRefSliceAt(2); + return ( + ref2Slice.preloadRefAt(1), + ref2Slice.preloadRefAt(0), + selfSlice.preloadAddressAt(Endpoint::_defaultSendMsglibManagerOffset), + ref2Slice.preloadRefAt(2) + ); +} + +;; (eid, dstEid, defaultExpiry, defaultReceiveLibInfo, timeoutReceiveLibInfo) +(int, int, int, cell, cell) Endpoint::getCommitPacketInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint32At(Endpoint::_eidOffset), + selfSlice.preloadUint32At(Endpoint::_dstEidOffset), + selfSlice.preloadUint64At(Endpoint::_defaultExpiryOffset), + selfSlice.preloadRefAt(2).cellPreloadRefAt(3), + selfSlice.preloadRefAt(3).cellPreloadRefAt(0) + ); +} + +;; ====================== Setters ===================== + +cell Endpoint::setDstEid(cell $self, int dstEid) impure inline { + slice selfSlice = $self.begin_parse(); + return begin_cell() + .store_slice(selfSlice.scutfirst(Endpoint::_dstEidOffset, 3)) + .store_uint32(dstEid) + .store_slice(selfSlice.sskipfirst(Endpoint::_numMsglibsOffset, 3)) + .end_cell(); +} + +;; ====================== Sanitize ===================== + +cell Endpoint::sanitize(cell $self) impure inline { + cell $baseStorage = $self.cl::get(Endpoint::baseStorage); + return Endpoint::New( + $self.cl::get(Endpoint::eid), + $self.cl::get(Endpoint::dstEid), + $baseStorage.cl::get
(BaseStorage::owner) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/endpointSetEpConfigDefaults.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/endpointSetEpConfigDefaults.fc new file mode 100644 index 00000000..9cb7722b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/endpointSetEpConfigDefaults.fc @@ -0,0 +1,443 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/AddMsglib.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../core/baseStorage.fc"; +#include "../../interfaces.fc"; + +#include "../../../funC++/constants.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/MsglibInfo.fc"; +#include "../../../classes/msgdata/InitEndpoint.fc"; +#include "../../../classes/msgdata/InitSmlConnection.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/SetEpConfig.fc"; +#include "../../msglibs/simpleMsglib/smlConnection/storage.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "endpoint"; } + +cell createContractStorage() impure { + setContractStorage(Endpoint::New(SRC_EID, DST_EID, getCaller())); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(md::InitEndpoint::New(MOCK_CHANNEL_CODE())); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================HELPER FUNCTIONS=============================== + +int getSmlConnectionAddress(int smlManagerAddress, cell $path) inline { + return computeContractAddress( + mockMsglibConnectionStorage(smlManagerAddress, $path), + MOCK_MSGLIB_CONNECTION_CODE() + ); +} + +() _addMsglib(int msglibManagerAddress) impure { + cell $addMsglib = md::AddMsglib::New(msglibManagerAddress, DST_EID); + addMsglib($addMsglib); +} + +() _addAndResolveMockMsglib(int msglibManagerAddress) impure { + int originalCaller = getCaller(); + spoofCaller(msglibManagerAddress); + _addMsglib(msglibManagerAddress); + getMsglibInfoCallback( + lz::MsglibInfo::New( + msglibManagerAddress, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(msglibManagerAddress, MOCK_SEND_PATH()) + ) + ); + spoofCaller(originalCaller); +} + +;;; ===============================TESTS========================================= + +(int, slice) setEpConfigDefaults::success::basic(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS); + $storage = getContractStorage(); + + cell $setEpConfigMd = MOCK_SET_EP_CONFIG_MD(true); + cell $path = MOCK_SEND_PATH(); + + cell $expectedStorage = $storage + .cl::set(Endpoint::defaultSendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set( + Endpoint::defaultSendLibInfo, + lz::MsglibInfo::New( + SEND_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(SEND_MSGLIB_MANAGER_ADDRESS, $path) + ) + ) + .cl::set( + Endpoint::defaultReceiveLibInfo, + lz::MsglibInfo::New( + RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(RECEIVE_MSGLIB_MANAGER_ADDRESS, $path) + ) + ) + .cl::set( + Endpoint::defaultTimeoutReceiveLibInfo, + lz::MsglibInfo::New( + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, $path) + ) + ) + .cl::set(Endpoint::defaultExpiry, MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY()) + ; + + return test::handler::shouldPass( + setEpConfigDefaults, + $setEpConfigMd, + unsafeTuple([ + 0, + _newAction( + Endpoint::event::EP_CONFIG_DEFAULTS_SET, + $setEpConfigMd + ) + ]), + $expectedStorage, + txnContext + ); +} + +;; these next 3 tests mean that we're setting the other two. +(int, slice) setEpConfigDefaults::success::noDefaultSendMsglib(cell $storage) impure { + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS); + + cell $path = MOCK_SEND_PATH(); + cell $setEpConfigMd = md::SetEpConfig::New( + false, + NULLADDRESS, + RECEIVE_MSGLIB_MANAGER_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); + + cell $expectedStorage = getContractStorage() + .cl::set( + Endpoint::defaultReceiveLibInfo, + lz::MsglibInfo::New( + RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(RECEIVE_MSGLIB_MANAGER_ADDRESS, $path) + ) + ) + .cl::set( + Endpoint::defaultTimeoutReceiveLibInfo, + lz::MsglibInfo::New( + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, $path) + ) + ) + .cl::set(Endpoint::defaultExpiry, MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY()) + ; + + return test::handler::shouldPass( + setEpConfigDefaults, + $setEpConfigMd, + unsafeTuple([ + 0, + _newAction( + Endpoint::event::EP_CONFIG_DEFAULTS_SET, + $setEpConfigMd + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigDefaults::success::noDefaultReceiveMsglib(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS); + + cell $path = MOCK_SEND_PATH(); + cell $setEpConfigMd = md::SetEpConfig::New( + false, + SEND_MSGLIB_MANAGER_ADDRESS, + NULLADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); + + cell $expectedStorage = getContractStorage() + .cl::set(Endpoint::defaultSendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set( + Endpoint::defaultSendLibInfo, + lz::MsglibInfo::New( + SEND_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(SEND_MSGLIB_MANAGER_ADDRESS, $path) + ) + ) + .cl::set( + Endpoint::defaultTimeoutReceiveLibInfo, + lz::MsglibInfo::New( + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, $path) + ) + ) + .cl::set(Endpoint::defaultExpiry, MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY()) + ; + + return test::handler::shouldPass( + setEpConfigDefaults, + $setEpConfigMd, + unsafeTuple([ + 0, + _newAction( + Endpoint::event::EP_CONFIG_DEFAULTS_SET, + $setEpConfigMd + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigDefaults::success::noDefaultTimeoutReceiveMsglib(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS); + + cell $path = MOCK_SEND_PATH(); + cell $setEpConfigMd = md::SetEpConfig::New( + false, + SEND_MSGLIB_MANAGER_ADDRESS, + RECEIVE_MSGLIB_MANAGER_ADDRESS, + NULLADDRESS, + 0 + ); + + cell $expectedStorage = getContractStorage() + .cl::set(Endpoint::defaultSendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set( + Endpoint::defaultSendLibInfo, + lz::MsglibInfo::New( + SEND_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(SEND_MSGLIB_MANAGER_ADDRESS, $path) + ) + ) + .cl::set( + Endpoint::defaultReceiveLibInfo, + lz::MsglibInfo::New( + RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(RECEIVE_MSGLIB_MANAGER_ADDRESS, $path) + ) + ) + .cl::set(Endpoint::defaultExpiry, 0) + ; + + return test::handler::shouldPass( + setEpConfigDefaults, + $setEpConfigMd, + unsafeTuple([ + 0, + _newAction( + Endpoint::event::EP_CONFIG_DEFAULTS_SET, + $setEpConfigMd + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) setEpConfigDefaults::revert::sendMsglibUnauthorized(cell $storage) impure { + cell $setEpConfigMd = md::SetEpConfig::New( + false, + SEND_MSGLIB_MANAGER_ADDRESS, + NULLADDRESS, + NULLADDRESS, + 0 + ); + + return test::handler::shouldFail( + setEpConfigDefaults, + $setEpConfigMd, + Endpoint::ERROR::unauthorizedMsglib + ); +} + +(int, slice) setEpConfigDefaults::revert::receiveMsglibUnauthorized(cell $storage) impure { + cell $setEpConfigMd = md::SetEpConfig::New( + false, + NULLADDRESS, + RECEIVE_MSGLIB_MANAGER_ADDRESS, + NULLADDRESS, + 0 + ); + + return test::handler::shouldFail( + setEpConfigDefaults, + $setEpConfigMd, + Endpoint::ERROR::unauthorizedMsglib + ); +} + +(int, slice) setEpConfigDefaults::revert::timeoutReceiveMsglibUnauthorized(cell $storage) impure { + cell $setEpConfigMd = md::SetEpConfig::New( + false, + NULLADDRESS, + NULLADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + 0 + ); + + return test::handler::shouldFail( + setEpConfigDefaults, + $setEpConfigMd, + Endpoint::ERROR::unauthorizedMsglib + ); +} + +(int, slice) setEpConfigDefaults::revert::sendMsglibUnresolved(cell $storage) impure { + _addMsglib(SEND_MSGLIB_MANAGER_ADDRESS); + cell $setEpConfigMd = md::SetEpConfig::New( + false, + SEND_MSGLIB_MANAGER_ADDRESS, + NULLADDRESS, + NULLADDRESS, + 0 + ); + + return test::handler::shouldFail( + setEpConfigDefaults, + $setEpConfigMd, + Endpoint::ERROR::unresolvedMsglib + ); +} + +(int, slice) setEpConfigDefaults::revert::receiveMsglibUnresolved(cell $storage) impure { + _addMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS); + cell $setEpConfigMd = md::SetEpConfig::New( + false, + NULLADDRESS, + RECEIVE_MSGLIB_MANAGER_ADDRESS, + NULLADDRESS, + 0 + ); + + return test::handler::shouldFail( + setEpConfigDefaults, + $setEpConfigMd, + Endpoint::ERROR::unresolvedMsglib + ); +} + +(int, slice) setEpConfigDefaults::revert::timeoutReceiveMsglibUnresolved(cell $storage) impure { + _addMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS); + cell $setEpConfigMd = md::SetEpConfig::New( + false, + NULLADDRESS, + NULLADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); + + return test::handler::shouldFail( + setEpConfigDefaults, + $setEpConfigMd, + Endpoint::ERROR::unresolvedMsglib + ); +} + +(int, slice) setEpConfigDefaults::revert::timeoutNullAndExpiryNonZero(cell $storage) impure { + cell $setEpConfigMd = md::SetEpConfig::New( + false, + NULLADDRESS, + NULLADDRESS, + NULLADDRESS, + 1 + ); + + return test::handler::shouldFail( + setEpConfigDefaults, + $setEpConfigMd, + Endpoint::ERROR::invalidExpiry + ); +} + +(int, slice) setEpConfigDefaults::revert::timeoutReceiveMsglibInvalidExpiry(cell $storage) impure { + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS); + cell $setEpConfigMd = md::SetEpConfig::New( + false, + NULLADDRESS, + NULLADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + now() - 10 + ); + + return test::handler::shouldFail( + setEpConfigDefaults, + $setEpConfigMd, + Endpoint::ERROR::invalidExpiry + ); +} + +(int, slice) setEpConfigDefaults::revert::sameTimeoutAndReceive(cell $storage) impure { + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS); + cell $setEpConfigMd = md::SetEpConfig::New( + false, + NULLADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); + + return test::handler::shouldFail( + setEpConfigDefaults, + $setEpConfigMd, + Endpoint::ERROR::sameTimeoutAndReceive + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- set ep config defaults + .tpush([setEpConfigDefaults::success::basic, "setEpConfigDefaults::success::basic"]) + .tpush([setEpConfigDefaults::success::noDefaultSendMsglib, "setEpConfigDefaults::success::noDefaultSendMsglib"]) + .tpush([setEpConfigDefaults::success::noDefaultReceiveMsglib, "setEpConfigDefaults::success::noDefaultReceiveMsglib"]) + .tpush([setEpConfigDefaults::success::noDefaultTimeoutReceiveMsglib, "setEpConfigDefaults::success::noDefaultTimeoutReceiveMsglib"]) + .tpush([setEpConfigDefaults::revert::sendMsglibUnauthorized, "setEpConfigDefaults::revert::sendMsglibUnauthorized"]) + .tpush([setEpConfigDefaults::revert::receiveMsglibUnauthorized, "setEpConfigDefaults::revert::receiveMsglibUnauthorized"]) + .tpush([setEpConfigDefaults::revert::timeoutReceiveMsglibUnauthorized, "setEpConfigDefaults::revert::timeoutReceiveMsglibUnauthorized"]) + .tpush([setEpConfigDefaults::revert::sendMsglibUnresolved, "setEpConfigDefaults::revert::sendMsglibUnresolved"]) + .tpush([setEpConfigDefaults::revert::receiveMsglibUnresolved, "setEpConfigDefaults::revert::receiveMsglibUnresolved"]) + .tpush([setEpConfigDefaults::revert::timeoutReceiveMsglibUnresolved, "setEpConfigDefaults::revert::timeoutReceiveMsglibUnresolved"]) + .tpush([setEpConfigDefaults::revert::timeoutNullAndExpiryNonZero, "setEpConfigDefaults::revert::timeoutNullAndExpiryNonZero"]) + .tpush([setEpConfigDefaults::revert::timeoutReceiveMsglibInvalidExpiry, "setEpConfigDefaults::revert::timeoutReceiveMsglibInvalidExpiry"]) + .tpush([setEpConfigDefaults::revert::sameTimeoutAndReceive, "setEpConfigDefaults::revert::sameTimeoutAndReceive"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/main.fc new file mode 100644 index 00000000..446bcd68 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/main.fc @@ -0,0 +1,668 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../classes/lz/Path.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../classes/msgdata/AddMsglib.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../core/baseStorage.fc"; +#include "../../interfaces.fc"; +#include "../../../funC++/classlib.fc"; + +#include "../../../funC++/constants.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/MsglibInfo.fc"; +#include "../../../classes/msgdata/InitEndpoint.fc"; +#include "../../../classes/msgdata/InitSmlConnection.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/SetEpConfig.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "endpoint"; } + +cell createContractStorage() impure { + setContractStorage(Endpoint::New(SRC_EID, DST_EID, getCaller())); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(md::InitEndpoint::New(MOCK_CHANNEL_CODE())); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================HELPER FUNCTIONS=============================== + +int getMsglibConnectionAddress(int msglibManagerAddress, cell $path) inline { + return computeContractAddress( + mockMsglibConnectionStorage(msglibManagerAddress, $path), + MOCK_MSGLIB_CONNECTION_CODE() + ); +} + +() _addMsglib(int msglibManagerAddress) impure { + cell $addMsglib = md::AddMsglib::New(msglibManagerAddress, DST_EID); + addMsglib($addMsglib); +} + +() _addAndResolveMockMsglib(int msglibManagerAddress, int msglibAddress) impure { + int originalCaller = getCaller(); + spoofCaller(msglibManagerAddress); + _addMsglib(msglibManagerAddress); + getMsglibInfoCallback( + lz::MsglibInfo::New( + msglibAddress, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(msglibManagerAddress, MOCK_SEND_PATH()) + ) + ); + spoofCaller(originalCaller); +} + +;;; ===============================TESTS========================================= + +(int, slice) resolveEpConfig::success::basic(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + cell $setEpConfigMd = MOCK_SET_EP_CONFIG_MD(true); + cell $epConfigResolved = CALCULATED_MOCK_RESOLVED_EP_CONFIG(true); + cell $path = MOCK_SEND_PATH(); + + ;; converts the manager addresses to the actual lib addresses + return test::shouldBeTrue( + compareObjectFields( + $epConfigResolved, + _getEpConfigFromManagerAddresses($setEpConfigMd, $path) + ) == -1 + ); +} + +(int, slice) endpointSend::success::basic(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + ;; Set default lib values + cell $setEpConfigMd = MOCK_SET_EP_CONFIG_MD(true); + setEpConfigDefaults($setEpConfigMd); + + cell $lzSend = MOCK_LZ_SEND(); + int channelAddress = _getChannelAddress($storage, MOCK_SEND_PATH()); + + return test::handler::shouldPass( + endpointSend, + $lzSend, + unsafeTuple([ + 0, + _newAction( + channelAddress, + Channel::OP::CHANNEL_SEND, + md::MdObj::New( + $lzSend, + lz::SendEpConfig::New( + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + getMsglibConnectionAddress(SEND_MSGLIB_MANAGER_ADDRESS, MOCK_SEND_PATH()) + ) + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) endpointSend::success::noDefaultSendMsglib(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + cell $lzSend = MOCK_LZ_SEND(); + int channelAddress = _getChannelAddress($storage, MOCK_SEND_PATH()); + + return test::handler::shouldPass( + endpointSend, + $lzSend, + unsafeTuple([ + 0, + _newAction( + channelAddress, + Channel::OP::CHANNEL_SEND, + md::MdObj::New($lzSend, lz::SendEpConfig::New(NULLADDRESS, NULLADDRESS, NULLADDRESS)) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) endpointCommitPacket::success::basic(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + ;; Set default lib values + cell $setEpConfigMd = MOCK_SET_EP_CONFIG_MD(true); + setEpConfigDefaults($setEpConfigMd); + + ;; the endpoint address in this context is 'self' + int endpointAddress = getContractAddress(); + + ;; generate the channel address + cell channelCode = $storage.cl::get(Endpoint::channelCode); + cell $channelStorage = Channel::New( + $storage.cl::get(Endpoint::baseStorage).cl::get
(BaseStorage::owner), + MOCK_SEND_PATH(), + endpointAddress + ); + + int channelAddress = computeContractAddress($channelStorage, channelCode); + + ;; generate the extended md + cell $packet = MOCK_RECEIVE_PACKET(); + + ;; extract the default config from storage, + ;; as its appended inside endpointCommitPacket to the output extende md + cell $expectedMdExtended = md::ExtendedMd::New( + $packet, + lz::ReceiveEpConfig::New( + getMsglibConnectionAddress(RECEIVE_MSGLIB_MANAGER_ADDRESS, MOCK_SEND_PATH()), + getMsglibConnectionAddress(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, MOCK_SEND_PATH()), + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; spoof as the connection + spoofCaller(RECEIVE_MSGLIB_CONNECTION_ADDRESS); + + return test::handler::shouldPass( + endpointCommitPacket, + $packet, + unsafeTuple([ + 0, + _newAction( + channelAddress, + Channel::OP::CHANNEL_COMMIT_PACKET, + $expectedMdExtended + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) endpointCommitPacket::success::noDefaultReceiveLib(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + ;; Set default lib values + cell $setEpConfigMd = MOCK_SET_EP_CONFIG_MD(true) + .cl::set(md::SetEpConfig::receiveMsglibManager, NULLADDRESS); + setEpConfigDefaults($setEpConfigMd); + + ;; the endpoint address in this context is 'self' + int endpointAddress = getContractAddress(); + + ;; generate the channel address + cell channelCode = $storage.cl::get(Endpoint::channelCode); + cell $channelStorage = Channel::New( + $storage.cl::get(Endpoint::baseStorage).cl::get
(BaseStorage::owner), + MOCK_SEND_PATH(), + endpointAddress + ); + int channelAddress = computeContractAddress($channelStorage, channelCode); + + ;; generate the extended md + cell $packet = MOCK_RECEIVE_PACKET(); + + ;; extract the default config from storage, + ;; as its appended inside endpointCommitPacket to the output extende md + cell $expectedMdExtended = md::ExtendedMd::New( + $packet, + lz::ReceiveEpConfig::New( + NULLADDRESS, + getMsglibConnectionAddress(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, MOCK_SEND_PATH()), + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; spoof as the connection + spoofCaller(RECEIVE_MSGLIB_CONNECTION_ADDRESS); + + return test::handler::shouldPass( + endpointCommitPacket, + $packet, + unsafeTuple([ + 0, + _newAction( + channelAddress, + Channel::OP::CHANNEL_COMMIT_PACKET, + $expectedMdExtended + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) endpointCommitPacket::success::noDefaultTimeoutReceiveMsglib(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + ;; Set default lib values + cell $setEpConfigMd = MOCK_SET_EP_CONFIG_MD(true) + .cl::set(md::SetEpConfig::timeoutReceiveMsglibManager, NULLADDRESS) + .cl::set(md::SetEpConfig::timeoutReceiveMsglibExpiry, 0); + setEpConfigDefaults($setEpConfigMd); + + ;; the endpoint address in this context is 'self' + int endpointAddress = getContractAddress(); + + ;; generate the channel address + cell channelCode = $storage.cl::get(Endpoint::channelCode); + cell $channelStorage = Channel::New( + $storage.cl::get(Endpoint::baseStorage).cl::get
(BaseStorage::owner), + MOCK_SEND_PATH(), + endpointAddress + ); + int channelAddress = computeContractAddress($channelStorage, channelCode); + + ;; generate the extended md + cell $packet = MOCK_RECEIVE_PACKET(); + + ;; extract the default config from storage, + ;; as its appended inside endpointCommitPacket to the output extende md + cell $expectedMdExtended = md::ExtendedMd::New( + $packet, + lz::ReceiveEpConfig::New( + getMsglibConnectionAddress(RECEIVE_MSGLIB_MANAGER_ADDRESS, MOCK_SEND_PATH()), + NULLADDRESS, + 0 + ), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; spoof as the connection + spoofCaller(RECEIVE_MSGLIB_CONNECTION_ADDRESS); + + return test::handler::shouldPass( + endpointCommitPacket, + $packet, + unsafeTuple([ + 0, + _newAction( + channelAddress, + Channel::OP::CHANNEL_COMMIT_PACKET, + $expectedMdExtended + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) endpointCommitPacket::success::noDefaultReceiveConfig(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + ;; the endpoint address in this context is 'self' + int endpointAddress = getContractAddress(); + + ;; generate the channel address + cell channelCode = $storage.cl::get(Endpoint::channelCode); + cell $channelStorage = Channel::New( + $storage.cl::get(Endpoint::baseStorage).cl::get
(BaseStorage::owner), + MOCK_SEND_PATH(), + endpointAddress + ); + int channelAddress = computeContractAddress($channelStorage, channelCode); + + ;; generate the extended md + cell $packet = MOCK_RECEIVE_PACKET(); + + ;; extract the default config from storage, + ;; as its appended inside endpointCommitPacket to the output extende md + cell $expectedMdExtended = md::ExtendedMd::New( + $packet, + lz::ReceiveEpConfig::New(NULLADDRESS, NULLADDRESS, 0), + RECEIVE_MSGLIB_CONNECTION_ADDRESS + ); + + ;; spoof as the connection + spoofCaller(RECEIVE_MSGLIB_CONNECTION_ADDRESS); + + return test::handler::shouldPass( + endpointCommitPacket, + $packet, + unsafeTuple([ + 0, + _newAction( + channelAddress, + Channel::OP::CHANNEL_COMMIT_PACKET, + $expectedMdExtended + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) endpointCommitPacket::revert::wrongPath(cell $storage) impure { + return test::handler::shouldFail( + endpointCommitPacket, + MOCK_SEND_PACKET(), ;; wrong packet, the path is backwards + Endpoint::ERROR::wrongPath + ); +} + +;; The first time this is called, it should set the manager to null, and then trigger a call to the manager +(int, slice) addMsglib::success::basic(cell $storage) impure { + cell $addMsglibMd = md::AddMsglib::New(MSGLIB_MANAGER_ADDRESS, DST_EID); + + ;; prepare the expected storage mutation + cell $expectedStorage = $storage + .cl::nestedDict256::setRef(Endpoint::msglibs, MSGLIB_MANAGER_ADDRESS, cl::nullObject()) + .cl::set(Endpoint::numMsglibs, $storage.cl::get(Endpoint::numMsglibs) + 1); + + return test::handler::shouldPass( + addMsglib, + $addMsglibMd, + unsafeTuple([ + 0, + _newAction( + MSGLIB_MANAGER_ADDRESS, + MsglibManager::OP::GET_MSGLIB_INFO, + $addMsglibMd + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) addMsglib::revert::numMsglibsExceeded(cell $storage) impure { + ;; set the num msgs libs to max + setContractStorage($storage.cl::set(Endpoint::numMsglibs, MAX_MSGLIBS)); + + return test::handler::shouldFail( + addMsglib, + md::AddMsglib::New(MSGLIB_MANAGER_ADDRESS, DST_EID), + Endpoint::ERROR::numMsglibsExceeded + ); +} + +(int, slice) addMsglib::success::idempotent(cell $storage) impure { + (int result1, slice slice1) = addMsglib::success::basic($storage); + (int result2, slice slice2) = addMsglib::success::basic($storage); + (int result3, slice slice3) = addMsglib::success::basic($storage); + + if ((result1 == TEST_SUCCESS) & (result1 == result2) & (result2 == result3) + & equal_slice_bits(slice1, slice2) & equal_slice_bits(slice2, slice3)) { + return (TEST_SUCCESS, ""); + } else { + return (TEST_FAILED, "addMsglib is not idempotent"); + } +} + +(int, slice) addMsglib::success::alreadyGotten(cell $storage) impure { + _addAndResolveMockMsglib(MSGLIB_MANAGER_ADDRESS, MSGLIB_ADDRESS); + + return test::handler::shouldPass( + addMsglib, + md::AddMsglib::New(MSGLIB_MANAGER_ADDRESS, DST_EID), + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) getMsglibInfoCallback::success::basic(cell $storage) impure { + _addMsglib(MSGLIB_MANAGER_ADDRESS); + + ;; spoof as the msglib manager address + spoofCaller(MSGLIB_MANAGER_ADDRESS); + + cell $msglibInfo = lz::MsglibInfo::New( + MSGLIB_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage( + MSGLIB_MANAGER_ADDRESS, + MOCK_SEND_PATH() + ) + ); + + ;; after the callback, it should set the state to the correct mapping + cell $expectedStorage = getContractStorage().cl::nestedDict256::setRef( + Endpoint::msglibs, + MSGLIB_MANAGER_ADDRESS, + $msglibInfo + ); + + return test::handler::shouldPass( + getMsglibInfoCallback, + $msglibInfo, + emptyActions(), + $expectedStorage, + txnContext + ); +} + +(int, slice) getMsglibInfoCallback::revert::msglibInfoExists(cell $storage) impure { + _addAndResolveMockMsglib(MSGLIB_MANAGER_ADDRESS, MSGLIB_ADDRESS); + + ;; spoof caller as the msglib manager + spoofCaller(MSGLIB_MANAGER_ADDRESS); + + cell $msglibInfo = lz::MsglibInfo::New( + MSGLIB_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage( + MSGLIB_MANAGER_ADDRESS, + MOCK_SEND_PATH() + ) + ); + + return test::handler::shouldFail( + getMsglibInfoCallback, + $msglibInfo, + Endpoint::ERROR::msglibInfoExists + ); +} + +(int, slice) setEpConfigOApp::success::basic(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + cell $path = MOCK_SEND_PATH(); + cell $mdSetEpConfig = MOCK_SET_EP_CONFIG_MD(true); + cell $resolvedEpConfigOApp = _getEpConfigFromManagerAddresses($mdSetEpConfig, $path); + + return test::handler::shouldPass( + setEpConfigOApp, + md::MdObj::New($mdSetEpConfig, $path), + unsafeTuple([ + 0, + _newAction( + _getChannelAddress($storage, $path), + Channel::OP::SET_EP_CONFIG_OAPP, + $resolvedEpConfigOApp + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) setEpConfigOApp::revert::sendMsglibUnauthorized(cell $storage) impure { + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + return test::handler::shouldFail( + setEpConfigOApp, + md::MdObj::New(MOCK_SET_EP_CONFIG_MD(true), MOCK_SEND_PATH()), + Endpoint::ERROR::unauthorizedMsglib + ); +} + +(int, slice) setEpConfigOApp::revert::receiveMsglibUnauthorized(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + return test::handler::shouldFail( + setEpConfigOApp, + md::MdObj::New(MOCK_SET_EP_CONFIG_MD(true), MOCK_SEND_PATH()), + Endpoint::ERROR::unauthorizedMsglib + ); +} + +(int, slice) setEpConfigOApp::revert::timeoutReceiveMsglibUnauthorized(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + + return test::handler::shouldFail( + setEpConfigOApp, + md::MdObj::New(MOCK_SET_EP_CONFIG_MD(true), MOCK_SEND_PATH()), + Endpoint::ERROR::unauthorizedMsglib + ); +} + +(int, slice) setEpConfigOApp::revert::sendMsglibUnresolved(cell $storage) impure { + _addMsglib(SEND_MSGLIB_MANAGER_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + return test::handler::shouldFail( + setEpConfigOApp, + md::MdObj::New(MOCK_SET_EP_CONFIG_MD(true), MOCK_SEND_PATH()), + Endpoint::ERROR::unresolvedMsglib + ); +} + +(int, slice) setEpConfigOApp::revert::receiveMsglibUnresolved(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS); + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + return test::handler::shouldFail( + setEpConfigOApp, + md::MdObj::New(MOCK_SET_EP_CONFIG_MD(true), MOCK_SEND_PATH()), + Endpoint::ERROR::unresolvedMsglib + ); +} + +(int, slice) setEpConfigOApp::revert::timeoutReceiveMsglibUnresolved(cell $storage) impure { + _addAndResolveMockMsglib(SEND_MSGLIB_MANAGER_ADDRESS, SEND_MSGLIB_ADDRESS); + _addAndResolveMockMsglib(RECEIVE_MSGLIB_MANAGER_ADDRESS, RECEIVE_MSGLIB_ADDRESS); + _addMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS); + + return test::handler::shouldFail( + setEpConfigOApp, + md::MdObj::New(MOCK_SET_EP_CONFIG_MD(true), MOCK_SEND_PATH()), + Endpoint::ERROR::unresolvedMsglib + ); +} + +(int, slice) setEpConfigOApp::revert::sameTimeoutAndReceive(cell $storage) impure { + _addAndResolveMockMsglib(TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, TIMEOUT_RECEIVE_MSGLIB_ADDRESS); + + cell $setEpConfigMd = md::SetEpConfig::New( + false, + NULLADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); + + return test::handler::shouldFail( + setEpConfigOApp, + md::MdObj::New($setEpConfigMd, MOCK_SEND_PATH()), + Endpoint::ERROR::sameTimeoutAndReceive + ); +} + +(int, slice) initialize::success::basic(cell $storage) impure { + setContractStorage(createContractStorage()); + forceAuthenticate(BASE_STORAGE_INDEX); + cell $storage = getContractStorage(); + cell $initEndpointMd = md::InitEndpoint::New(MOCK_CHANNEL_CODE()); + cell $expectedStorage = $storage + .cl::set( + BASE_STORAGE_INDEX, + $storage.cl::get(BASE_STORAGE_INDEX) + .cl::set(BaseStorage::initialized, true) + ) + .cl::set(Endpoint::channelCode, MOCK_CHANNEL_CODE()) + .cl::set( + Endpoint::channelStorageInit, + Channel::New( + getOwner(), + lz::Path::endpointPath( + $storage.cl::get(Endpoint::eid), + $storage.cl::get(Endpoint::dstEid) + ), + getContractAddress() + ) + ); + return test::handler::shouldPass( + initialize, + $initEndpointMd, + emptyActions(), + $expectedStorage, + txnContext + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- resolve Ep Config + .tpush([resolveEpConfig::success::basic, "resolveEpConfig::success::basic"]) + ;; -- endpointSend + .tpush([endpointSend::success::basic, "send::endpointSend::basic"]) + .tpush([endpointSend::success::noDefaultSendMsglib, "send::endpointSend::noDefaults"]) + ;; -- endpointCommitPacket + .tpush([endpointCommitPacket::success::basic, "endpointCommitPacket::success::basic"]) + .tpush([endpointCommitPacket::success::noDefaultReceiveLib, "endpointCommitPacket::success::noDefaultReceiveLib"]) + .tpush([endpointCommitPacket::success::noDefaultTimeoutReceiveMsglib, "endpointCommitPacket::success::noDefaultTimeoutReceiveMsglib"]) + .tpush([endpointCommitPacket::success::noDefaultReceiveConfig, "endpointCommitPacket::success::noDefaultReceiveConfig"]) + .tpush([endpointCommitPacket::revert::wrongPath, "endpointCommitPacket::revert::wrongPath"]) + ;; -- add msglib + .tpush([addMsglib::success::basic, "addMsglib::success::basic"]) + .tpush([addMsglib::revert::numMsglibsExceeded, "addMsglib::revert::numMsglibsExceeded"]) + .tpush([addMsglib::success::idempotent, "addMsglib::success::idempotent"]) + .tpush([addMsglib::success::alreadyGotten, "addMsglib::success::alreadyGotten"]) + ;; -- get msglib info callback + .tpush([getMsglibInfoCallback::success::basic, "getMsglibInfoCallback::success::basic"]) + .tpush([getMsglibInfoCallback::revert::msglibInfoExists, "getMsglibInfoCallback::revert::msglibInfoExists"]) + ;; -- set ep config + .tpush([setEpConfigOApp::success::basic, "setEpConfigOApp::success::basic"]) + .tpush([setEpConfigOApp::revert::sendMsglibUnauthorized, "setEpConfigOApp::revert::sendMsglibUnauthorized"]) + .tpush([setEpConfigOApp::revert::receiveMsglibUnauthorized, "setEpConfigOApp::revert::receiveMsglibUnauthorized"]) + .tpush([setEpConfigOApp::revert::timeoutReceiveMsglibUnauthorized, "setEpConfigOApp::revert::timeoutReceiveMsglibUnauthorized"]) + .tpush([setEpConfigOApp::revert::sendMsglibUnresolved, "setEpConfigOApp::revert::sendMsglibUnresolved"]) + .tpush([setEpConfigOApp::revert::receiveMsglibUnresolved, "setEpConfigOApp::revert::receiveMsglibUnresolved"]) + .tpush([setEpConfigOApp::revert::timeoutReceiveMsglibUnresolved, "setEpConfigOApp::revert::timeoutReceiveMsglibUnresolved"]) + .tpush([setEpConfigOApp::revert::sameTimeoutAndReceive, "setEpConfigOApp::revert::sameTimeoutAndReceive"]) + ;; -- initialize + .tpush([initialize::success::basic, "initialize::success::basic"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/permissions.fc new file mode 100644 index 00000000..0a5d265f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/permissions.fc @@ -0,0 +1,130 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/EpConfig.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../interfaces.fc"; +#include "../../../classes/msgdata/AddMsglib.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Endpoint::permissions"; } + +cell createContractStorage() impure { + setContractStorage(Endpoint::New( + SRC_EID, + DST_EID, + getCaller() + )); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(md::InitEndpoint::New(MOCK_CHANNEL_CODE())); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) checkPermissions::endpointSend::success::basic(cell $storage) impure { + spoofCaller(SRC_OAPP); + return test::permissions::shouldPass( + Endpoint::OP::ENDPOINT_SEND, + MOCK_LZ_SEND() + ); +} + +(int, slice) checkPermissions::endpointSend::revert::notOApp(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Endpoint::OP::ENDPOINT_SEND, + MOCK_LZ_SEND() + ); +} + +(int, slice) checkPermissions::endpointCommitPacket::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass(Endpoint::OP::ENDPOINT_COMMIT_PACKET, cl::nullObject()); +} + +(int, slice) checkPermissions::setEpConfigOApp::success::basic(cell $storage) impure { + return test::permissions::shouldPass(Endpoint::OP::SET_EP_CONFIG_OAPP, cl::nullObject()); +} + +(int, slice) checkPermissions::setEpConfigOApp::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Endpoint::OP::SET_EP_CONFIG_OAPP, cl::nullObject()); +} + +(int, slice) checkPermissions::setEpConfigDefaults::success::basic(cell $storage) impure { + return test::permissions::shouldPass(Endpoint::OP::SET_EP_CONFIG_DEFAULTS, cl::nullObject()); +} + +(int, slice) checkPermissions::setEpConfigDefaults::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Endpoint::OP::SET_EP_CONFIG_DEFAULTS, cl::nullObject()); +} + +(int, slice) checkPermissions::addMsglib::success::basic(cell $storage) impure { + return test::permissions::shouldPass(Endpoint::OP::ADD_MSGLIB, cl::nullObject()); +} + +(int, slice) checkPermissions::addMsglib::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail(Endpoint::OP::ADD_MSGLIB, cl::nullObject()); +} + +(int, slice) checkPermissions::getMsglibInfoCallback::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + cell $addMsglibMd = md::AddMsglib::New(MSGLIB_MANAGER_ADDRESS, DST_EID); + addMsglib($addMsglibMd); + spoofCaller(MSGLIB_MANAGER_ADDRESS); + return test::permissions::shouldPass(Endpoint::OP::GET_MSGLIB_INFO_CALLBACK, cl::nullObject()); +} + +(int, slice) checkPermissions::getMsglibInfoCallback::revert::unauthorizedMsglib(cell $storage) impure { + ;; Permissionless, can be called by anyone + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldFail(Endpoint::OP::GET_MSGLIB_INFO_CALLBACK, cl::nullObject()); +} + +(int, slice) checkPermissions::default::revert::invalidOpCode(cell $storage) impure { + ;; this is a generic opcode that we should NOT allow + return test::permissions::shouldFail(OP::RANDOM, cl::nullObject()); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; -- endpoint send + .tpush([checkPermissions::endpointSend::success::basic, "checkPermissions::endpointSend::success::basic"]) + .tpush([checkPermissions::endpointSend::revert::notOApp, "checkPermissions::endpointSend::revert::notOApp"]) + ;; -- endpoint commit packet + .tpush([checkPermissions::endpointCommitPacket::success::basic, "checkPermissions::endpointCommitPacket::success::basic"]) + ;; -- set ep config oapp + .tpush([checkPermissions::setEpConfigOApp::success::basic, "checkPermissions::setEpConfigOApp::success::basic"]) + .tpush([checkPermissions::setEpConfigOApp::revert::notOwner, "checkPermissions::setEpConfigOApp::revert::notOwner"]) + ;; -- set ep config defaults + .tpush([checkPermissions::setEpConfigDefaults::success::basic, "checkPermissions::setEpConfigDefaults::success::basic"]) + .tpush([checkPermissions::setEpConfigDefaults::revert::notOwner, "ccheckPermissions::setEpConfigDefaults::revert::notOwner"]) + ;; -- add msglib + .tpush([checkPermissions::addMsglib::success::basic, "checkPermissions::addMsglib::success::basic"]) + .tpush([checkPermissions::addMsglib::revert::notOwner, "checkPermissions::addMsglib::revert::notOwner"]) + ;; -- get msglib info callback + .tpush([checkPermissions::getMsglibInfoCallback::success::basic, "checkPermissions::getMsglibInfoCallback::success::basic"]) + .tpush([checkPermissions::getMsglibInfoCallback::revert::unauthorizedMsglib, "checkPermissions::getMsglibInfoCallback::revert::unauthorizedMsglib"]) + ;; -- invalid opcode + .tpush([checkPermissions::default::revert::invalidOpCode, "checkPermissions::default::revert::invalidOpCode"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/serde.fc new file mode 100644 index 00000000..be9b109a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/endpoint/tests/serde.fc @@ -0,0 +1,167 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../funC++/testutils.fc"; + +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Endpoint::Serde"; } + +;; Endpoint: Has 7 getters, +;; Has 2 multi-getter (deserializer), +(int, slice) Serde::Endpoint::getBaseStorage(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + return test::getRef::equal( + $endpoint, + Endpoint::getBaseStorage, + Endpoint::baseStorage + ); +} + +(int, slice) Serde::Endpoint::getMsglibs(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + return test::getRef::equal( + $endpoint, + Endpoint::getMsglibs, + Endpoint::msglibs + ); +} + +(int, slice) Serde::Endpoint::getChannelCode(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + return test::getRef::equal( + $endpoint, + Endpoint::getChannelCode, + Endpoint::channelCode + ); +} + +(int, slice) Serde::Endpoint::getChannelStorageInit(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + return test::getRef::equal( + $endpoint, + Endpoint::getChannelStorageInit, + Endpoint::channelStorageInit + ); +} + +(int, slice) Serde::Endpoint::getDefaultSendLibInfo(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + return test::getRef::equal( + $endpoint, + Endpoint::getDefaultSendLibInfo, + Endpoint::defaultSendLibInfo + ); +} + +(int, slice) Serde::Endpoint::getDefaultReceiveLibInfo(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + return test::getRef::equal( + $endpoint, + Endpoint::getDefaultReceiveLibInfo, + Endpoint::defaultReceiveLibInfo + ); +} + +(int, slice) Serde::Endpoint::getDefaultTimeoutReceiveLibInfo(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + return test::getRef::equal( + $endpoint, + Endpoint::getDefaultTimeoutReceiveLibInfo, + Endpoint::defaultTimeoutReceiveLibInfo + ); +} + +(int, slice) Serde::Endpoint::getSendInformation(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + + ( + cell $channelStorageInit, + cell $channelCode, + int defaultSendMsglibManager, + cell $defaultSendLibInfo + ) = Endpoint::getSendConfiguration($endpoint); + + return test::multiget::equal( + $endpoint, + unsafeTuple([ + Endpoint::channelStorageInit, + Endpoint::channelCode, + Endpoint::defaultSendMsglibManager, + Endpoint::defaultSendLibInfo + ]), + unsafeTuple([$channelStorageInit, $channelCode, defaultSendMsglibManager, $defaultSendLibInfo]) + ); +} + +(int, slice) Serde::Endpoint::getCommitPacketInformation(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS) + .cl::set(Endpoint::defaultReceiveLibInfo, + lz::MsglibInfo::New( + RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage(RECEIVE_MSGLIB_MANAGER_ADDRESS, MOCK_RECEIVE_PATH() + ) + ) + ); + + ( + int $eid, + int $dstEid, + int $defaultExpiry, + cell $defaultReceiveLibInfo, + cell $timeoutReceiveLibInfo + ) = Endpoint::getCommitPacketInformation($endpoint); + + return test::multiget::equal( + $endpoint, + unsafeTuple([ + Endpoint::eid, + Endpoint::dstEid, + Endpoint::defaultExpiry, + Endpoint::defaultReceiveLibInfo, + Endpoint::defaultTimeoutReceiveLibInfo + ]), + unsafeTuple([$eid, $dstEid, $defaultExpiry, $defaultReceiveLibInfo, $timeoutReceiveLibInfo]) + ); +} + +(int, slice) Serde::Endpoint::setDstEid(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + + return test::set::equal( + $endpoint.cl::set(Endpoint::dstEid, DST_EID + 1), + $endpoint.Endpoint::setDstEid(DST_EID + 1) + ); +} + +(int, slice) Serde::Endpoint::sanitize(cell $unused) impure { + cell $endpoint = Endpoint::New(SRC_EID, DST_EID, CONTROLLER_ADDRESS); + + return test::build::equal( + $endpoint, + Endpoint::sanitize(_dupWithGarbage($endpoint)) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::Endpoint::getBaseStorage, "Serde::Endpoint::getBaseStorage"]) + .tpush([Serde::Endpoint::getMsglibs, "Serde::Endpoint::getMsglibs"]) + .tpush([Serde::Endpoint::getChannelCode, "Serde::Endpoint::getChannelCode"]) + .tpush([Serde::Endpoint::getChannelStorageInit, "Serde::Endpoint::getChannelStorageInit"]) + .tpush([Serde::Endpoint::getDefaultSendLibInfo, "Serde::Endpoint::getDefaultSendLibInfo"]) + .tpush([Serde::Endpoint::getDefaultReceiveLibInfo, "Serde::Endpoint::getDefaultReceiveLibInfo"]) + .tpush([Serde::Endpoint::getDefaultTimeoutReceiveLibInfo, "Serde::Endpoint::getDefaultTimeoutReceiveLibInfo"]) + .tpush([Serde::Endpoint::getSendInformation, "Serde::Endpoint::getSendInformation"]) + .tpush([Serde::Endpoint::getCommitPacketInformation, "Serde::Endpoint::getCommitPacketInformation"]) + .tpush([Serde::Endpoint::setDstEid, "Serde::Endpoint::setDstEid"]) + .tpush([Serde::Endpoint::sanitize, "Serde::Endpoint::sanitize"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/interfaces.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/interfaces.fc new file mode 100644 index 00000000..1a7d2391 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/interfaces.fc @@ -0,0 +1,4 @@ +#include "channel/interface.fc"; +#include "controller/interface.fc"; +#include "endpoint/interface.fc"; +#include "msglibs/interface.fc"; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/BytesDecoder.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/BytesDecoder.fc new file mode 100644 index 00000000..cdc294c2 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/BytesDecoder.fc @@ -0,0 +1,135 @@ +#include "../../classes/lz/Packet.fc"; + +const int BytesDecoder::ERROR::invalidOptionSize = 1121; +const int BytesDecoder::ERROR::malformedOptions = 1122; +const int BytesDecoder::ERROR::malformedArray = 1123; + +;; n_bytes should be <= 32 +(slice, int) BytesDecoder::loadBytes(slice decoder, int nBytes) impure inline method_id { + int sliceBits = decoder.slice_bits(); + if ((decoder.slice_refs() > 1) | (sliceBits % 8 != 0)) { + throw(BytesDecoder::ERROR::malformedOptions); + } + int sliceBytes = sliceBits / 8; + if (sliceBytes >= nBytes) { + return decoder.load_uint(nBytes * 8); + } else { + (decoder, int valueHeader) = decoder.load_uint(sliceBits); + int remainingBytes = nBytes - sliceBytes; + (slice next_data, int valueTail) = BytesDecoder::loadBytes( + decoder.preload_first_ref().begin_parse(), + remainingBytes + ); + slice fullData = begin_cell() + .store_uint(valueHeader, sliceBits) + .store_uint(valueTail, remainingBytes * 8) + .as_slice(); + return (next_data, fullData.preload_uint(nBytes * 8)); + } +} + +;; byte- and cell- align the data +cell BytesDecoder::compactRemainder(slice data) impure inline { + int moduloBits = data.slice_bits(); ;; "misaligned" portion + throw_unless( + BytesDecoder::ERROR::malformedArray, + (data.slice_refs() <= 1) & ((moduloBits % 8) == 0) + ); + + if (moduloBits == 0) { + return empty_cell(); + } + + if (data.slice_refs() == 0) { + return begin_cell().store_slice(data).end_cell(); + } + + tuple retData = empty_tuple(); + + while (data.slice_refs() > 0) { + slice nextData = data.preload_first_ref().begin_parse(); + int sliceBits = nextData.slice_bits(); + int remainingBits = ((sliceBits + moduloBits) <= 1016) ? sliceBits : (1016 - moduloBits); + if (nextData.slice_refs() > 0) { + throw_if(BytesDecoder::ERROR::malformedArray, nextData.slice_bits() != 1016); + } + throw_unless( + BytesDecoder::ERROR::malformedArray, + (nextData.slice_refs() <= 1) & (sliceBits > 0) & (sliceBits % 8 == 0) + ); + retData~tpush( + begin_cell() + .store_slice(data.scutlast(moduloBits, 0)) + .store_slice(nextData.scutfirst(remainingBits, 0)) + ); + if ((nextData.slice_refs() == 0) & ((sliceBits - remainingBits) > 0)) { + retData~tpush( + begin_cell().store_slice(nextData.sskipfirst(remainingBits, 0)) + ); + } + data = nextData; + } + int idx = retData.tlen() - 1; + builder ret = retData.at(idx); + while (idx > 0) { + idx -= 1; + ret = retData.at(idx).store_ref(ret.end_cell()); + } + return ret.end_cell(); +} + +;; returns (optionType, option, optionsTail) +(int, cell, cell) BytesDecoder::nextOption(cell options) impure inline { + slice parsingOptions = options.begin_parse(); + (parsingOptions, int executorId) = BytesDecoder::loadBytes(parsingOptions, 1); + (parsingOptions, int optionSizeInBytes) = BytesDecoder::loadBytes(parsingOptions, 2); + (parsingOptions, int optionType) = BytesDecoder::loadBytes(parsingOptions, 1); + (parsingOptions, int option) = BytesDecoder::loadBytes(parsingOptions, optionSizeInBytes); + return ( + optionType, + begin_cell().store_uint(option, optionSizeInBytes * 8).end_cell(), + begin_cell().store_slice(parsingOptions).end_cell() ;; should retain the ref + ); +} + +;; (gas, value) +(int, int) BytesDecoder::decode(cell option) impure inline { + slice optionSlice = option.begin_parse(); + int optionSizeInBytes = optionSlice.slice_bits() / 8; + if ((optionSizeInBytes != 16) & (optionSizeInBytes != 32)) { + throw(BytesDecoder::ERROR::invalidOptionSize); + } + if (optionSizeInBytes == 256 / 8) { + return (optionSlice~load_uint128(), optionSlice~load_uint128()); + } + return (optionSlice~load_uint128(), 0); +} + +;; (amount, receiver) +(int, int) BytesDecoder::decode(cell option) impure inline { + slice parsingOption = option.begin_parse(); + int optionBytes = parsingOption.slice_bits() * 8; + throw_unless(BytesDecoder::ERROR::invalidOptionSize, optionBytes == (32 + 16)); + return ( + parsingOption~load_uint128(), ;; amount + parsingOption~load_uint256() ;; receiver + ); +} + +;; returns lz::Packet +cell BytesDecoder::decode(cell encodedPacket) impure inline method_id { + slice parsingPacket = encodedPacket.begin_parse(); + (parsingPacket, int packetVersion) = BytesDecoder::loadBytes(parsingPacket, 1); + (parsingPacket, int nonce) = BytesDecoder::loadBytes(parsingPacket, 8); + (parsingPacket, int srcEid) = BytesDecoder::loadBytes(parsingPacket, 4); + (parsingPacket, int srcOApp) = BytesDecoder::loadBytes(parsingPacket, 32); + (parsingPacket, int dstEid) = BytesDecoder::loadBytes(parsingPacket, 4); + (parsingPacket, int dstOApp) = BytesDecoder::loadBytes(parsingPacket, 32); + (parsingPacket, int guid) = BytesDecoder::loadBytes(parsingPacket, 32); + + return lz::Packet::New( + lz::Path::New(srcEid, srcOApp, dstEid, dstOApp), + BytesDecoder::compactRemainder(parsingPacket), + nonce + ).cl::set(lz::Packet::guid, guid); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/BytesEncoder.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/BytesEncoder.fc new file mode 100644 index 00000000..3ee622b4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/BytesEncoder.fc @@ -0,0 +1,151 @@ +#include "../../classes/lz/Path.fc"; +#include "../../classes/lz/Packet.fc"; + +const int BytesEncoder::ERROR::malformed_subtree = 1217; +const int BytesEncoder::ERROR::malformed_data = 1218; +;; these are calculated relative to their position in the BytesEncoder::build function +const int PacketV1::nonceOffsetBytes = 1; +const int PacketV1::nonceBytes = 8; +const int PacketV1::guidOffsetBytes = 81; +const int PacketV1::guidBytes = 32; + +;; keeps a stack of builders on a tuple +tuple BytesEncoder::New() inline { + return unsafeTuple([begin_cell()]); +} + +;; appends a slice to the bytes encoder +tuple BytesEncoder::feed(tuple self, slice data) impure inline { + while (~ data.is_null()) { + ;; verifies it is a linked list and only has one reference + throw_if(BytesEncoder::ERROR::malformed_subtree, data.slice_refs() > 1); + + ;; grabs the last builder on the tuple stack + int curBuilderIdx = self.tlen() - 1; + builder curBuilder = self.at(curBuilderIdx); + + ;; calculates the size in bytes of the current builder and the data + int curBuilderBytes = curBuilder.builder_bits() / 8; + int dataWidthBytes = data.slice_bits() / 8; + int fillsCurrentBuilder = (curBuilderBytes + dataWidthBytes) == 127; + + ;; if there is a partail byte at the end the check below makes it throw if it is + throw_if(BytesEncoder::ERROR::malformed_data, (data.slice_bits() % 8) != 0); + + ;; if the current builder + the size of data are greater + ;; than 127 bytes, we need to break it up into multiple builders + if ((curBuilderBytes + dataWidthBytes) > 127) { + ;; calculate the first X bytes to add to the current builder to fill it + int partialDataWidth = 127 - curBuilderBytes; + + ;; the remaining bytes length inside data after we move the reference to + ;; where the partialDataWidth left off + dataWidthBytes = dataWidthBytes - partialDataWidth; + + ;; store the leading X bytes and saves to the tuple stack + self = self.tset( + curBuilderIdx, + curBuilder + .store_slice(data.preload_bits(partialDataWidth * 8)) + ); + + ;; moves the pointer + data = data.skip_bits(partialDataWidth * 8); + curBuilderIdx += 1; + + ;; adds a new builder to the tuple stack + curBuilder = begin_cell(); + self~tpush(curBuilder); + } + ;; If we entered the previous if statement, we have a fresh builder and less than + ;; 127 bytes remaining at this slice in the data linked list. This will take the remaining + ;; data inside the data slice and store in the builder. + ;; If we didn't enter the previous if statement it will append the remaining bytes + ;; of the data slice to the builder. + ;; THhen updates the tuple stack with the updated writes + self = self.tset(curBuilderIdx, curBuilder.store_slice(data.sdskipfirst(0))); + + ;; if data linked list has more data extend the data stack and move the pointer of + ;; data to the next item in the list. + ifnot (data.slice_refs_empty?()) { + + ;; if the current builder is full add a new builer on the tuple stack + if (fillsCurrentBuilder) { + self = self.tpush(begin_cell()); + } + ;; move the pointer + data = data.preload_first_ref().begin_parse(); + } else { + ;; cue to end the while loop, the data has been stored in the bytes encoded tuple stack + data = null(); + } + } + + return self; +} + +;; builds from the bottom up (of the stack) since data is built from the leaf node of a tree +cell BytesEncoder::serialize(tuple self) impure inline { + int idx = self.tlen() - 1; + ;; if we're at the head + if (idx == 0) { + return self.at(idx).end_cell(); + } + ;; if second to last + if (idx == 1) { + return self.at(0).store_ref(self.at(1).end_cell()).end_cell(); + } + builder last = self.at(idx); + builder secondLast = self.at(idx - 1); + while (idx > 1) { + idx -= 1; + last = secondLast.store_ref(last.end_cell()); + secondLast = self.at(idx); + } + return secondLast.store_ref(last.end_cell()).end_cell(); +} + +tuple BytesEncoder::feed(tuple self, int data) impure inline { + return self.BytesEncoder::feed(begin_cell().store_uint8(data).as_slice()); +} + +tuple BytesEncoder::feed(tuple self, int data) impure inline { + return self.BytesEncoder::feed(begin_cell().store_uint16(data).as_slice()); +} + +tuple BytesEncoder::feed(tuple self, int data) impure inline { + return self.BytesEncoder::feed(begin_cell().store_uint32(data).as_slice()); +} + +tuple BytesEncoder::feed(tuple self, int data) impure inline { + return self.BytesEncoder::feed(begin_cell().store_uint64(data).as_slice()); +} + +tuple BytesEncoder::feed(tuple self, int data) impure inline { + return self.BytesEncoder::feed(begin_cell().store_uint128(data).as_slice()); +} + +tuple BytesEncoder::feed(tuple self, int data) impure inline { + return self.BytesEncoder::feed(begin_cell().store_uint256(data).as_slice()); +} + +tuple BytesEncoder::feed(tuple self, cell data) impure inline { + return self.BytesEncoder::feed(data.begin_parse()); +} + +tuple BytesEncoder::build(cell $packet) impure inline { + (cell $path, cell $message, int nonce, int guid) = $packet.lz::Packet::deserialize(); + (int srcEid, int srcOApp, int dstEid, int dstOApp) = $path.lz::Path::deserialize(); + return unsafeTuple([ + begin_cell() ;; 113 bytes of the 127 + .store_uint8(1) ;; packetVersion uint8 + .store_uint64(nonce) ;; nonce uint64 + .store_uint32(srcEid) ;; srcEid uint32 + .store_uint256(srcOApp) ;; sender bytes32 + .store_uint32(dstEid) ;; dstEid uint32 + .store_uint256(dstOApp) ;; receiver bytes32 + .store_uint256(guid) ;; guid bytes32 + ]).BytesEncoder::feed( + $message ;; message []bytes + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/interface.fc new file mode 100644 index 00000000..b74a9ccd --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/interface.fc @@ -0,0 +1,22 @@ +;; All msglibs must have the path in their storage as field 1 +const int MsglibConnection::PathFieldIdx = 1; + +;; All msglibs are required to have a connection and a manager +const int MsglibManager::OP::GET_MSGLIB_INFO = "MsglibManager::OP::GET_MSGLIB_INFO"c; + +const int MsglibManager::OP::DEPLOY_CONNECTION = "MsglibManager::OP::DEPLOY_CONNECTION"c; + +const int MsglibManager::OP::SET_OAPP_MSGLIB_SEND_CONFIG = "MsglibManager::OP::SET_OAPP_MSGLIB_SEND_CONFIG"c; + +;; Set the connection MSGLIB config +;; called by OApp, seeded by SENDER +const int MsglibManager::OP::SET_OAPP_MSGLIB_RECEIVE_CONFIG = "MsglibManager::OP::SET_OAPP_MSGLIB_RECEIVE_CONFIG"c; + +const int Msglib::OP::RETURN_QUOTE = "Msglib::OP::RETURN_QUOTE"c; + +const int MsglibConnection::OP::MSGLIB_CONNECTION_QUOTE = "MsglibConnection::OP::MSGLIB_CONNECTION_QUOTE"c; +const int MsglibConnection::OP::MSGLIB_CONNECTION_SEND = "MsglibConnection::OP::MSGLIB_CONNECTION_SEND"c; +const int MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK = "MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK"c; +const int MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE = "MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE"c; + +const int Msglib::ERROR::onlyChannel = 288; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/handler.fc new file mode 100644 index 00000000..8cd3bf53 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/handler.fc @@ -0,0 +1,125 @@ +#include "../../../core/abstract/protocolHandler.fc"; + +#include "../../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../../classes/msgdata/InitSmlConnection.fc"; +#include "../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../classes/msgdata/MdObj.fc"; + + +#include "../../../endpoint/interface.fc"; +#include "../../interface.fc"; +#include "../smlManager/interface.fc"; +#include "interface.fc"; +#include "../../../../funC++/classlib.fc"; + +;;; ================HELPER FUNCTIONS======================== + +int getChannelAddress() impure inline { + return getContractStorage().cl::get
(SmlConnection::channelAddress); +} + +() assertChannel() impure inline { + throw_unless( + Msglib::ERROR::onlyChannel, + getCaller() == getChannelAddress() + ); +} + +;;; ================INTERFACE FUNCTIONS===================== + +(cell, tuple) _initialize(cell $initSmlConnection) impure inline { + (cell $storage, tuple actions) = preamble(); + int channelAddress = $initSmlConnection + .cl::get
(md::InitSmlConnection::channelAddress); + return ( + $storage.cl::set(SmlConnection::channelAddress, channelAddress), + actions + ); +} + +int _getEventSink() inline { + return getContractAddress(); +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline method_id { + if (op == MsglibConnection::OP::MSGLIB_CONNECTION_SEND) { + return assertChannel(); + } elseif (op == MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK) { + return assertChannel(); + } elseif (op == SmlConnection::OP::SML_CONNECTION_COMMIT_PACKET) { + return assertOwner(); + } elseif (op == MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE) { + return assertChannel(); + } else { + ;; we must put a check for all opcodes to make sure we don't + ;; mistakenly miss an opp code's permissions + throw(BaseInterface::ERROR::invalidOpcode); + } +} + +;;; =================HANDLERS================================ + +;; @in channel/handler.fc/send +;; @in_md LzSend +;; @out smlManager/handler.fc/msglibSend +;; @out_md ExtendedMd(LzSend, initialStorage, channelAddress) +tuple msglibConnectionSend(cell $lzSend) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + actions~pushAction( + getOwner(), ;; smlManager + SmlManager::OP::SML_MANAGER_SEND, + md::ExtendedMd::New( + $lzSend, + getInitialStorage(), + $storage.cl::get
(SmlConnection::channelAddress) + ) + ); + + return actions; +} + +;; @in smlManager/handler.fc/smlManagerCommitPacket +;; @in_md msglibCommitPacket +;; @out endpoint/handler.fc/commitPacket +;; @out_md MdObj { packet, initialStorage } +tuple smlConnectionCommitPacket(cell $mdAddress) impure inline method_id { + (_, tuple actions) = preamble(); + + actions~pushAction( + $mdAddress.cl::get
(md::MdAddress::address), + Endpoint::OP::ENDPOINT_COMMIT_PACKET, + $mdAddress.cl::get(md::MdAddress::md) + ); + + return actions; +} + +;; @in channel/handler.fc/commitPacket +;; @in_md msglibCommitPacketCallback +;; @out smlManager/handler.fc/smlManagerCommitPacketCallback +tuple msglibConnectionCommitPacketCallback(cell $mdObj) impure inline method_id { + (_, tuple actions) = preamble(); + + actions~pushAction( + getOwner(), + SmlManager::OP::SML_MANAGER_COMMIT_PACKET_CALLBACK, + $mdObj + ); + + return actions; +} + +tuple syncChannelState(cell $mdObj) impure inline method_id { + (_, tuple actions) = preamble(); + + actions~pushAction( + getChannelAddress(), + MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE, + $mdObj + ); + + return actions; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/interface.fc new file mode 100644 index 00000000..820d9acf --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/interface.fc @@ -0,0 +1,3 @@ +#include "storage.fc"; + +const int SmlConnection::OP::SML_CONNECTION_COMMIT_PACKET = "SmlConnection::OP::SML_CONNECTION_COMMIT_PACKET"c; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/main.fc new file mode 100644 index 00000000..0a895511 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/main.fc @@ -0,0 +1,19 @@ +#include "../../../core/abstract/protocolMain.fc"; +#include "../../interface.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == MsglibConnection::OP::MSGLIB_CONNECTION_SEND) { + return msglibConnectionSend($md); + } elseif (op == SmlConnection::OP::SML_CONNECTION_COMMIT_PACKET) { + return smlConnectionCommitPacket($md); + } elseif (op == MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK) { + return msglibConnectionCommitPacketCallback($md); + } elseif (op == MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE) { + return syncChannelState($md); + } + throw(BaseInterface::ERROR::invalidOpcode); + return empty_tuple(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/storage.fc new file mode 100644 index 00000000..c1512f58 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/storage.fc @@ -0,0 +1,20 @@ +#include "../../../core/baseStorage.fc"; +#include "../../../../funC++/classlib.fc"; + +const int SmlConnection::NAME = "smlConn"u; + +const int SmlConnection::baseStorage = 0; +const int SmlConnection::path = 1; +const int SmlConnection::channelAddress = 2; + +;; @owner LayerZero admin EOA +cell SmlConnection::New(int owner, cell $path) method_id { + return cl::declare( + SmlConnection::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; SmlConnection::baseStorage + [cl::t::objRef, $path], ;; SmlConnection::path + [cl::t::address, NULLADDRESS] ;; SmlConnection::channelAddress + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/tests/main.fc new file mode 100644 index 00000000..7edefb99 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/tests/main.fc @@ -0,0 +1,74 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../../interface.fc"; + +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/handlerCore.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../../tests/consts.fc"; +#include "../../../../../classes/msgdata/LzSend.fc"; +#include "../../../../../classes/msgdata/InitSmlConnection.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "SmlConnection"; } + +cell createContractStorage() impure { + setContractStorage(SmlConnection::New(getCaller(), MOCK_SEND_PATH())); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(md::InitSmlConnection::New(CHANNEL_ADDRESS)); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================HELPER FUNCTIONS=============================== + +(int, slice) msglibConnectionSend::success::basic(cell $storage) impure { + cell $lzSend = MOCK_LZ_SEND(); + + return test::handler::shouldPass( + msglibConnectionSend, + $lzSend, + unsafeTuple([ + 0, + _newAction( + getOwner(), ;; SML manager == SML (eid "shard") + SmlManager::OP::SML_MANAGER_SEND, + md::ExtendedMd::New( + $lzSend, + getInitialStorage(), + $storage.cl::get
(SmlConnection::channelAddress) + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; (int, slice) msglibCommitPacketCallback::success::basic(cell $storage) impure { +;; return test::handler::shouldPass( +;; msglibCommitPacketCallback, +;; MOCK_PACKET(), +;; emptyActions(), +;; $storage, +;; txnContext +;; ); +;; } + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([msglibConnectionSend::success::basic, "msglibConnectionSend::success::basic"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/tests/permissions.fc new file mode 100644 index 00000000..e63a0b7c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlConnection/tests/permissions.fc @@ -0,0 +1,52 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../interfaces.fc"; +#include "../../../interface.fc"; +#include "../../../../../../tests/consts.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "SmlConnection::permissions"; } + +cell createContractStorage() impure { + setContractStorage(SmlConnection::New(getCaller(), MOCK_SEND_PATH())); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize(md::InitSmlConnection::New(CHANNEL_ADDRESS)); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) checkPermissions::msglibConnectionSend::success::basic(cell $storage) impure { + spoofCaller(CHANNEL_ADDRESS); + return test::permissions::shouldPass(MsglibConnection::OP::MSGLIB_CONNECTION_SEND, + MOCK_LZ_SEND() + ); +} + +(int, slice) checkPermissions::msglibConnectionSend::revert::onlyChannel(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldFail(MsglibConnection::OP::MSGLIB_CONNECTION_SEND, + MOCK_LZ_SEND() + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([checkPermissions::msglibConnectionSend::success::basic, "checkPermissions::msglibConnectionSend::success::basic"]) + .tpush([checkPermissions::msglibConnectionSend::revert::onlyChannel, "checkPermissions::msglibConnectionSend::revert::onlyChannel"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/handler.fc new file mode 100644 index 00000000..7d4fffb3 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/handler.fc @@ -0,0 +1,278 @@ +;;; ============================================================= +;; Simple Message Library is intended for testing purposes ONLY +;; It is purposely left unsecure on nearly every interface +;;; ============================================================= + +#include "../../../core/abstract/protocolHandler.fc"; +#include "../../../../funC++/baseInterface.fc"; + +#include "../../BytesEncoder.fc"; + +#include "../../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../../classes/msgdata/MdObj.fc"; +#include "../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../classes/msgdata/AddMsglib.fc"; +#include "../../../../classes/msgdata/MsglibSendCallback.fc"; +#include "../../../../classes/msgdata/SetSmlManagerConfig.fc"; +#include "../../../../classes/msgdata/Deploy.fc"; +#include "../../../../classes/msgdata/InitSmlConnection.fc"; +#include "../../../../classes/msgdata/LzSend.fc"; +#include "../../../../classes/lz/Path.fc"; +#include "../../../../classes/lz/MsglibInfo.fc"; +#include "../../../../classes/lz/Packet.fc"; +#include "../../../../classes/lz/SmlJobAssigned.fc"; + +#include "../../../endpoint/storage.fc"; +#include "../../../channel/storage.fc"; +#include "../smlConnection/interface.fc"; +#include "../smlConnection/storage.fc"; + +#include "../../interface.fc"; +#include "../../../interfaces.fc"; +#include "interface.fc"; +#include "storage.fc"; +#include "../../../channel/interface.fc"; + +;;; ====================HELPER FUNCTIONS==================== + +;; Derive the endpoint address for a given destination EID +int _deriveEndpointAddress(int dstEid) impure inline method_id { + cell $storage = getContractStorage(); + cell $endpointStorage = Endpoint::New( + $storage.cl::get(SmlManager::eid), + dstEid, + $storage.cl::get
(SmlManager::controllerAddress) + ); + return computeContractAddress( + $endpointStorage, + $storage.cl::get(SmlManager::endpointCode) + ); +} + +;; Derive the channel address for a given path +int _deriveChannelAddress(cell $path) impure inline method_id { + cell $storage = getContractStorage(); + int dstEid = $path.cl::get(lz::Path::dstEid); + int endpointAddress = _deriveEndpointAddress(dstEid); + return computeContractAddress( + Channel::New( + $storage.cl::get
(SmlManager::controllerAddress), + $path, + endpointAddress + ), + $storage.cl::get(SmlManager::channelCode) + ); +} + +int _deriveConnectionAddress(cell $path) impure inline method_id { + return computeContractAddress( + SmlConnection::New( + getContractAddress(), + $path + ), + getContractStorage().cl::get(SmlManager::smlConnectionCode) + ); +} + +() assertConnection(cell $extendedMd) impure inline { + throw_unless( + SmlManager::ERROR::onlyConnection, + getCaller() == computeContractAddress( + $extendedMd.cl::get(md::ExtendedMd::obj), + getContractStorage().cl::get(SmlManager::smlConnectionCode) + ) + ); +} + +;;; ================INTERFACE FUNCTIONS===================== + +(cell, tuple) _initialize(cell $md) impure inline { + return preamble(); +} + +int _getEventSink() inline { + return getContractAddress(); +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + if ( + (op == SmlManager::OP::SET_MSGLIB_CONFIG) + | (op == SmlManager::OP::SML_MANAGER_COMMIT_PACKET) + | (op == MsglibManager::OP::GET_MSGLIB_INFO) + | (op == MsglibManager::OP::DEPLOY_CONNECTION) + | (op == SmlManager::OP::SML_MANAGER_COMMIT_PACKET_CALLBACK) + ) { + ;; op code + ;; open and public calls + return (); + } elseif ( + (op == SmlManager::OP::SML_MANAGER_SEND) + ) { + ;; assert connection + assertConnection($md); + } else { + ;; we must put a check for all opcodes to make sure we don't + ;; mistakenly miss an opp code's permissions + throw(BaseInterface::ERROR::invalidOpcode); + } +} + +;;; ==========================HANDLERS===================================== + +;; @in permissionless +;; @in_md ExtendedMd { SetSmlManagerConfig, unused, unused } +tuple setMsglibConfig(cell $mdExtended) impure inline method_id { + ;; permissionless setting of fees, this is NOT safe for a real msglib + (cell $storage, tuple actions) = preamble(); + + ;; These other extended values arent used, + ;; but it makes the config call adhere to the expected interface + ;; Pull the config md from the extendedMd that gets passed + cell $SmlManagerConfig = $mdExtended.cl::get(md::ExtendedMd::md); + + setContractStorage( + $storage + .cl::set( + SmlManager::nativeFee, + $SmlManagerConfig.cl::get(md::SetSmlManagerConfig::nativeFee) + ) + .cl::set( + SmlManager::zroFee, + $SmlManagerConfig.cl::get(md::SetSmlManagerConfig::zroFee) + ) + ); + + actions~pushAction(SmlManager::event::SET_MSGLIB_CONFIG, $SmlManagerConfig); + + return actions; +} + +;; @in channel/handler.fc/send +;; @in_md LzSend +;; @out channel/handler.fc/msglibSendCallback +;; @out_md msglibSendCallback +tuple smlManagerSend(cell $extendedMd) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $lzSend = $extendedMd.cl::get(md::ExtendedMd::md); + + int nativeFee = $storage.cl::get(SmlManager::nativeFee); + + ;;-------------------------- + ;; PACKET V1 CODEC + ;;-------------------------- + cell $packetObj = $lzSend.cl::get(md::LzSend::packet); + cell packet = BytesEncoder::build($packetObj) + .BytesEncoder::serialize(); + + cell $msglibSendCallbackMd = md::MsglibSendCallback::New( + nativeFee, + $storage.cl::get(SmlManager::zroFee), + $lzSend, + packet, ;; encoded Packet + serializePayees( + unsafeTuple([ + [getContractAddress(), nativeFee] + ]) + ), + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + lz::SmlJobAssigned::New(nativeFee), + Channel::NO_ERROR + ); + + actions~pushAction( + $extendedMd.cl::get
(md::ExtendedMd::forwardingAddress), ;; channelAddress + Channel::OP::MSGLIB_SEND_CALLBACK, + $msglibSendCallbackMd + ); + + return actions; +} + +;; @in permissionless external message +;; @in_md msglibCommitPacket { packet_header: [objRef], message: [cellRef] } +;; @out_success => smlConnection/handler.fc/msglibConnectionCommitPacket +;; @out_md_success => ExtendedMd(ep_commit_verification, _, _) +;; @out_failure => caller (refund, failure) +tuple smlManagerCommitPacket(cell $mdAddress) impure inline method_id { + (_, tuple actions) = preamble(); + + cell $packet = $mdAddress.cl::get(md::MdAddress::md); + + int connectionAddress = _deriveConnectionAddress( + lz::Path::reverse($packet.cl::get(lz::Packet::path)) + ); + + ;; @notice In Simple Msglib, + ;; we purposely do not verify that the message hash is the same as the stored message hash + actions~pushAction( + connectionAddress, + SmlConnection::OP::SML_CONNECTION_COMMIT_PACKET, + $mdAddress + ); + + return actions; +} + +;; @in endpoint/handler.fc/getMsgLib +;; @in_md AddMsglib +;; @out endpoint/handler.fc/getMsglibInfoCallback +;; @out_md MsglibInfo +tuple getMsgLibInfo(cell $addMsglibMd) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $msglibInfo = lz::MsglibInfo::New( + getContractAddress(), ;; for SML, the manager IS the msglib + $storage.cl::get(SmlManager::smlConnectionCode), + SmlConnection::New( + getContractAddress(), ;; connection is owned by the manager + lz::Path::endpointPath( + $storage.cl::get(SmlManager::eid), + $addMsglibMd.cl::get(md::AddMsglib::dstEid) + ) + ) + ); + + actions~pushAction( + getCaller(), + Endpoint::OP::GET_MSGLIB_INFO_CALLBACK, + $msglibInfo + ); + + return actions; +} + +;; @in smlConnection +;; @in_md MdObj { Packet, SmlConnection (initial storage) } +tuple smlManagerCommitPacketCallback(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + return actions; +} + +;; @in permissionless (OApp) +;; @in_md Deploy +tuple deployConnection(cell $deploy) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $path = lz::Path::New( + $storage.cl::get(SmlManager::eid), + getCaller(), + $deploy.cl::get(md::Deploy::dstEid), + $deploy.cl::get
(md::Deploy::dstOApp) + ); + + actions~pushAction( + $storage.cl::get(SmlManager::smlConnectionCode), + SmlConnection::New(getContractAddress(), $path), + $deploy.cl::get(md::Deploy::initialDeposit), + BaseInterface::OP::INITIALIZE, + md::InitSmlConnection::New(_deriveChannelAddress($path)), + 0 + ); + return actions; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/interface.fc new file mode 100644 index 00000000..d12a8fa9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/interface.fc @@ -0,0 +1,14 @@ +#include "../../../../funC++/constants.fc"; + +const int SmlManager::OP::SET_MSGLIB_CONFIG = "SmlManager::OP::SET_MSGLIB_CONFIG"c; +const int SmlManager::OP::SML_MANAGER_COMMIT_PACKET_CALLBACK = "SmlManager::OP::SML_MANAGER_COMMIT_PACKET_CALLBACK"c; +const int SmlManager::OP::SML_MANAGER_COMMIT_PACKET = "SmlManager::OP::SML_MANAGER_COMMIT_PACKET"c; + +const int SmlManager::OP::SML_MANAGER_SEND = "SmlManager::OP::SML_MANAGER_SEND"c; + +;; EVENTS +const int SmlManager::event::SET_MSGLIB_CONFIG = "SET_MSGLIB_CONFIG"u; + +const int SmlManager::ERROR::packetHeaderNotFound = "packetHeaderNotFound"c & ERRORCODE_MASK; +const int SmlManager::ERROR::optionsNotEmpty = "optionsNotEmpty"c & ERRORCODE_MASK; +const int SmlManager::ERROR::onlyConnection = "onlyConnection"c & ERRORCODE_MASK; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/main.fc new file mode 100644 index 00000000..ca754bc8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/main.fc @@ -0,0 +1,24 @@ +#include "handler.fc"; +#include "interface.fc"; + +#include "../../../core/abstract/protocolMain.fc"; +#include "../../interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == SmlManager::OP::SET_MSGLIB_CONFIG) { + return setMsglibConfig($md); + } elseif (op == SmlManager::OP::SML_MANAGER_SEND) { + return smlManagerSend($md); + } elseif (op == SmlManager::OP::SML_MANAGER_COMMIT_PACKET) { + return smlManagerCommitPacket($md); + } elseif (op == SmlManager::OP::SML_MANAGER_COMMIT_PACKET_CALLBACK) { + return smlManagerCommitPacketCallback($md); + } elseif (op == MsglibManager::OP::GET_MSGLIB_INFO) { + return getMsgLibInfo($md); + } elseif (op == MsglibManager::OP::DEPLOY_CONNECTION) { + return deployConnection($md); + } else { + throw(BaseInterface::ERROR::invalidOpcode); + } + return empty_tuple(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/storage.fc new file mode 100644 index 00000000..85323824 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/storage.fc @@ -0,0 +1,38 @@ +#include "../../../core/baseStorage.fc"; + +const int SmlManager::NAME = "smlMgr"u; + +const int SmlManager::baseStorage = 0; + +const int SmlManager::eid = 1; +const int SmlManager::verison = 2; + +;; configurations, only owner? +const int SmlManager::nativeFee = 3; +const int SmlManager::zroFee = 4; + +;; Dict256 hash(header) => hash(message) +const int SmlManager::packets = 5; +const int SmlManager::controllerAddress = 6; +const int SmlManager::endpointCode = 7; +const int SmlManager::channelCode = 8; +const int SmlManager::smlConnectionCode = 9; + +;; @owner LayerZero admin EOA +cell SmlManager::New(int owner, int eid, int version, int controllerAddress, cell endpointCode, cell channelCode, cell smlConnectionCode) method_id { + return cl::declare( + SmlManager::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; SmlManager::baseStorage + [cl::t::uint32, eid], ;; SmlManager::eid + [cl::t::uint8, version], ;; SmlManager::version + [cl::t::coins, 0], ;; SmlManager::nativeFee + [cl::t::coins, 0], ;; SmlManager::zroFee + [cl::t::dict256, cl::dict256::New()], ;; SmlManager::packets + [cl::t::address, controllerAddress], ;; SmlManager::controllerAddress + [cl::t::cellRef, endpointCode], ;; SmlManager::endpointCode + [cl::t::cellRef, channelCode], ;; SmlManager::channelCode + [cl::t::cellRef, smlConnectionCode] ;; SmlManager::smlConnectionCode + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/tests/main.fc new file mode 100644 index 00000000..7803625d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/tests/main.fc @@ -0,0 +1,202 @@ +#include "../handler.fc"; +#include "../interface.fc"; + +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/SetSmlManagerConfig.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../channel/interface.fc"; +#include "../../../../../funC++/handlerCore.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../../tests/consts.fc"; +#include "../../../../../classes/msgdata/LzSend.fc"; +#include "../../../../../classes/msgdata/MsglibSendCallback.fc"; + +#include "../../../../../classes/lz/SmlJobAssigned.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "SmlManager"; } + +cell createContractStorage() impure { + setContractStorage( + SmlManager::New( + getCaller(), + SRC_EID, + SML_MANAGER_VERSION, + CONTROLLER_ADDRESS, + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE(), + MOCK_MSGLIB_CONNECTION_CODE() + ) + ); + return getContractStorage(); +} + +;; do not need to implement +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================HELPER FUNCTIONS=============================== + +(int, slice) setMsglibConfig::success::basic(cell $storage) impure { + ;; setting of zroFee + cell $SmlManagerConfig = md::SetSmlManagerConfig::New(NATIVE_FEE, ZRO_FEE); + ;; These other values arent used, but it makes the config call adhere to the expected interface + cell $mdExtended = md::ExtendedMd::New($SmlManagerConfig, cl::nullObject(), NULLADDRESS); + + cell $expectedStorage = $storage + .cl::set(SmlManager::nativeFee, NATIVE_FEE) + .cl::set(SmlManager::zroFee, ZRO_FEE); + + return test::handler::shouldPass( + setMsglibConfig, + $mdExtended, + unsafeTuple([ + 0, + _newAction(SmlManager::event::SET_MSGLIB_CONFIG, $SmlManagerConfig) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) msglibCommitPacket::success::basic(cell $storage) impure { + cell $packet = MOCK_SEND_PACKET(); + setContractStorage( + $storage + .cl::nestedDict256::setRef( + SmlManager::packets, + $packet.cl::hash(), + cl::nullObject() + ) + ); + int connectionAddress = _deriveConnectionAddress( + lz::Path::reverse($packet.cl::get(lz::Packet::path)) + ); + + cell $mdAddress = md::MdAddress::New( + MOCK_SEND_PACKET(), + ENDPOINT_ADDRESS + ); + + return test::handler::shouldPass( + smlManagerCommitPacket, + $mdAddress, + unsafeTuple([ + 0, + _newAction( + connectionAddress, + SmlConnection::OP::SML_CONNECTION_COMMIT_PACKET, + $mdAddress + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) msglibSend::success::basic(cell $storage) impure { + cell $lzSend = MOCK_LZ_SEND(); + + ;; exctract the expected values + int nativeFee = $storage.cl::get(SmlManager::nativeFee); + int zroFee = $storage.cl::get(SmlManager::zroFee); + cell payees = serializePayees( + unsafeTuple([[getContractAddress(), nativeFee]]) + ); + + cell $packet = $lzSend.cl::get(md::LzSend::packet); + cell packetEncoded = BytesEncoder::build($packet) + .BytesEncoder::serialize(); + + int channelAddress = _deriveChannelAddress( + $packet.cl::get(lz::Packet::path) + ); + + cell $extendedMd = md::ExtendedMd::New( + $lzSend, + cl::nullObject(), + channelAddress + ); + + return test::handler::shouldPass( + smlManagerSend, + $extendedMd, + unsafeTuple([ + 0, + _newAction( + channelAddress, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MdAddress::New( + md::MsglibSendCallback::New( + nativeFee, + zroFee, + $lzSend, + packetEncoded, + payees, + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + lz::SmlJobAssigned::New(nativeFee), + Channel::NO_ERROR + ), + getContractAddress() + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) getMsgLibInfo::success::basic(cell $storage) impure { + cell $addMsglibMd = md::AddMsglib::New(SEND_MSGLIB_MANAGER_ADDRESS, DST_EID); + + cell $msglibInfo = lz::MsglibInfo::New( + getContractAddress(), ;; for SML, the manager IS the msglib + $storage.cl::get(SmlManager::smlConnectionCode), + SmlConnection::New( + getContractAddress(), ;; connection is owned by the manager + lz::Path::endpointPath( + $storage.cl::get(SmlManager::eid), + $addMsglibMd.cl::get(md::AddMsglib::dstEid) + ) + ) + ); + + return test::handler::shouldPass( + getMsgLibInfo, + $addMsglibMd, + unsafeTuple([ + 0, + _newAction( + getCaller(), + Endpoint::OP::GET_MSGLIB_INFO_CALLBACK, + $msglibInfo + ) + ]), + $storage, + txnContext + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([setMsglibConfig::success::basic, "setMsglibConfig::success::basic"]) + .tpush([msglibCommitPacket::success::basic, "msglibCommitPacket::success::basic"]) + .tpush([msglibSend::success::basic, "msglibSend::success::basic"]) + .tpush([getMsgLibInfo::success::basic, "getMsgLibInfo::success::basic"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/tests/permissions.fc new file mode 100644 index 00000000..5b88e1eb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/simpleMsglib/smlManager/tests/permissions.fc @@ -0,0 +1,145 @@ +#include "../handler.fc"; +#include "../interface.fc"; + +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../interfaces.fc"; +#include "../../../interface.fc"; +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/mocks.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "SmlManager::permissions"; } + +cell createContractStorage() impure { + setContractStorage( + SmlManager::New( + getCaller(), + SRC_EID, + SML_MANAGER_VERSION, + CONTROLLER_ADDRESS, + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE(), + MOCK_MSGLIB_CONNECTION_CODE() + ) + ); + return getContractStorage(); +} + +;; do not need to implement +() _createInitializedStorage() impure { + initialize(cl::nullObject()); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) checkPermissions::setMsglibConfig::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + SmlManager::OP::SET_MSGLIB_CONFIG, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::smlManagerSend::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + cell $extendedMd = md::ExtendedMd::New( + cl::nullObject(), + SmlConnection::New(getContractAddress(), MOCK_SEND_PATH()), + NULLADDRESS + ); + int connectionAddress = _deriveConnectionAddress(MOCK_SEND_PATH()); + spoofCaller(connectionAddress); + return test::permissions::shouldPass( + SmlManager::OP::SML_MANAGER_SEND, + $extendedMd + ); +} + +(int, slice) checkPermissions::smlManagerSend::revert::notConnection(cell $storage) impure { + cell $extendedMd = md::ExtendedMd::New( + cl::nullObject(), + SmlConnection::New(getContractAddress(), MOCK_SEND_PATH()), + NULLADDRESS + ); + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldFail( + SmlManager::OP::SML_MANAGER_SEND, + $extendedMd + ); +} + +(int, slice) checkPermissions::smlManagerCommitPacket::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + SmlManager::OP::SML_MANAGER_COMMIT_PACKET, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::smlManagerCommitPacketCallback::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + cell $extendedMd = md::ExtendedMd::New( + cl::nullObject(), + SmlConnection::New(getContractAddress(), MOCK_SEND_PATH()), + NULLADDRESS + ); + int connectionAddress = _deriveConnectionAddress(MOCK_SEND_PATH()); + spoofCaller(connectionAddress); + return test::permissions::shouldPass( + SmlManager::OP::SML_MANAGER_COMMIT_PACKET_CALLBACK, + $extendedMd + ); +} + +(int, slice) checkPermissions::smlManagerCommitPacketCallback::revert::onlyConnection(cell $storage) impure { + cell $extendedMd = md::ExtendedMd::New( + cl::nullObject(), + SmlConnection::New(getContractAddress(), MOCK_SEND_PATH()), + NULLADDRESS + ); + + ;; Permissionless, can be called by anyone + spoofCaller(ARBITRARY_ADDRESS); + + return test::permissions::shouldFail( + SmlManager::OP::SML_MANAGER_COMMIT_PACKET_CALLBACK, + $extendedMd + ); +} + +(int, slice) checkPermissions::getMsgLibInfo::success::basic(cell $storage) impure { + ;; Permissionless, can be called by anyone + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + MsglibManager::OP::GET_MSGLIB_INFO, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::default::revert::invalidOpCode(cell $storage) impure { + ;; this is a generic opcode that we should NOT allow + return test::permissions::shouldFail(OP::RANDOM, cl::nullObject()); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([checkPermissions::setMsglibConfig::success::basic, "checkPermissions::setMsglibConfig::success::basic"]) + .tpush([checkPermissions::smlManagerSend::success::basic, "checkPermissions::smlManagerSend::success::basic"]) + .tpush([checkPermissions::smlManagerSend::revert::notConnection, "checkPermissions::smlManagerSend::revert::notConnection"]) + .tpush([checkPermissions::smlManagerCommitPacket::success::basic, "checkPermissions::smlManagerCommitPacket::success::basic"]) + .tpush([checkPermissions::smlManagerCommitPacketCallback::success::basic, "checkPermissions::smlManagerCommitPacketCallback::success::basic"]) + ;; .tpush([checkPermissions::smlManagerCommitPacketCallback::revert::onlyConnection, "checkPermissions::smlManagerCommitPacketCallback::revert::onlyConnection"]) + .tpush([checkPermissions::getMsgLibInfo::success::basic, "checkPermissions::getMsgLibInfo::success::basic"]) + .tpush([checkPermissions::default::revert::invalidOpCode, "checkPermissions::default::revert::invalidOpCode"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/tests/msglibPacketCodec.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/tests/msglibPacketCodec.fc new file mode 100644 index 00000000..52b96809 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/tests/msglibPacketCodec.fc @@ -0,0 +1,184 @@ +#include "../BytesEncoder.fc"; +#include "../BytesDecoder.fc"; +#include "../../../../tests/testMain.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../classes/lz/Packet.fc"; + +slice _testName() { return "msglibBytesEncoder"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +() _dumpLinkedList(cell ll) impure { + slice lls = ll.begin_parse(); + int idx = 0; + while (lls.slice_refs_empty?() == false) { + str::console::log("Linked list idx: ", idx); + ~strdump(lls); + lls = lls.preload_first_ref().begin_parse(); + idx += 1; + } + str::console::log("Linked list idx: ", idx); + ~dump(lls); +} + +(int, slice) BytesEncoder::basic(cell $args) impure { + cell encodedPacket = BytesEncoder::New() + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::feed("hello, world!") + .BytesEncoder::serialize(); + ;; _dumpLinkedList(encodedPacket); + int condition = true; + return test::shouldBeTrue(condition); +} + +;; does a simple byte-aligned cell get encoded to itself? +(int, slice) BytesEncoder::IdempotentCell(cell $args) impure { + cell data = MOCK_CHANNEL_CODE(); + cell encodedData = BytesEncoder::New() + .BytesEncoder::feed(data) + .BytesEncoder::serialize(); + + return test::shouldBeTrue( + data.cell_hash() == encodedData.cell_hash() + ); +} + +(int, slice) BytesDecoder::SingleOption::success(cell $args) impure { + int expectedOptionType = 5; + int expectedOptionInt = 100; + cell options = begin_cell() + ;; option 1 -- start + .store_uint8(123) ;; executor id + .store_uint16(1) ;; optionsize (bytes) + .store_uint8(expectedOptionType) ;; option type + .store_uint8(expectedOptionInt) ;; option bits + ;; option 1 -- end + .end_cell(); + + (int optionType, cell nextOption, options) = BytesDecoder::nextOption(options); + int optionInt = nextOption.begin_parse().preload_uint(8); + + return test::shouldBeTrue( + (optionType == expectedOptionType) + & (expectedOptionInt == optionInt) + ); +} + +(int, slice) BytesDecoder::TwoOptions::success(cell $args) impure { + int expectedOptionType = 6; + int expectedOptionInt = 101; + cell options = begin_cell() + ;; option 1 -- start + .store_uint8(123) ;; executor id + .store_uint16(1) ;; optionsize (bytes) + .store_uint8(expectedOptionType - 1) ;; option type + .store_uint8(expectedOptionInt - 1) ;; option bits + ;; option 1 -- end + ;; option 2 -- start + .store_uint8(111) ;; executor id + .store_uint16(2) ;; optionsize (bytes) + .store_uint8(expectedOptionType) ;; option type + .store_uint16(expectedOptionInt) ;; option bits + ;; option 2 -- end + .end_cell(); + + ;; skip option 1 + (_, _, options) = BytesDecoder::nextOption(options); + (int optionType, cell nextOption, options) = BytesDecoder::nextOption(options); + int optionInt = nextOption.begin_parse().preload_uint(16); + + return test::shouldBeTrue( + (optionType == expectedOptionType) + & (optionInt == expectedOptionInt) + ); +} + +(int, slice) BytesDecoder::SplitOption::success(cell $args) impure { + int expectedOptionType = 6; + int expectedOptionInt = 101; + cell options = begin_cell() + ;; option 1 -- start + .store_uint8(123) ;; executor id + .store_uint16(2) ;; optionsize (bytes) + .store_uint8(expectedOptionType) ;; option type + ;; store option itself in the next cell + .store_ref( + begin_cell() + .store_uint16(expectedOptionInt) ;; option bits + .end_cell() + ) + ;; option 1 -- end + .end_cell(); + + (int optionType, cell nextOption, options) = BytesDecoder::nextOption(options); + int optionInt = nextOption.begin_parse().preload_uint(16); + + return test::shouldBeTrue( + (optionType == expectedOptionType) + & (optionInt == expectedOptionInt) + ); +} + +(int, slice) EncoderDecoder::Packet::success(cell $args) impure { + cell $path = MOCK_SEND_PATH(); + + int guid = lz::Packet::calculateGuid($path, NONCE); + cell $packet = lz::Packet::New( + $path, + MOCK_MESSAGE(), + NONCE + ).cl::set(lz::Packet::guid, guid); + + cell encodedPacket = BytesEncoder::build($packet) + .BytesEncoder::serialize(); + + cell $decodedPacket = BytesDecoder::decode(encodedPacket); + + return test::shouldBeTrue( + $packet.cell_hash() == $decodedPacket.cell_hash() + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([BytesEncoder::basic, "BytesEncoder::basic"]) + .tpush([BytesEncoder::IdempotentCell, "BytesEncoder::IdempotentCell"]) + .tpush([BytesDecoder::SingleOption::success, "BytesDecoder::SingleOption::success"]) + .tpush([BytesDecoder::TwoOptions::success, "BytesDecoder::TwoOptions::success"]) + .tpush([BytesDecoder::SplitOption::success, "BytesDecoder::SplitOption::success"]) + .tpush([EncoderDecoder::Packet::success, "EncoderDecoder::Packet::success"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/callbackOpcodes.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/callbackOpcodes.fc new file mode 100644 index 00000000..9c4be5ec --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/callbackOpcodes.fc @@ -0,0 +1 @@ +const int UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK = "UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK"c; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/feeLibInterface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/feeLibInterface.fc new file mode 100644 index 00000000..325e5e5d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/feeLibInterface.fc @@ -0,0 +1,12 @@ +;; these numbers *MUST* be updated every time the interface function names change +;; nativeFee, event +;; (int, tuple) ulnWorker::quote(cell $storage, cell $borrowedStorage, cell $lzSend) method_id; +const int UlnWorkerInterface::quote = 107686; ;; ulnWorker::quote + +;; tuple ulnWorker::setConfig(cell $storage, cell $md) method_id; +const int UlnWorkerInterface::setConfig = 95983; ;; ulnWorker::setConfig + +const int UlnWorkerInterface::ERROR::UNKNOWN_OPTIONS = 2100; +const int UlnWorkerInterface::ERROR::ZERO_LZ_COMPOSE_GAS_PROVIDED = 2101; +const int UlnWorkerInterface::ERROR::INVALID_OPTIONS = 2102; +const int UlnWorkerInterface::ERROR::NATIVE_CAP_EXCEEDED = 2103; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/feeLibUtils.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/feeLibUtils.fc new file mode 100644 index 00000000..6dc76ef3 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/feeLibUtils.fc @@ -0,0 +1,52 @@ +;; 1) If RUNVM successful and M (number of returnable elements is set) it returns M elements. If M not set - returns all. +;; 2) if RUNVM successful but there is not enough elements on stack (M) it is considered as exception in child VM with exit_code=-3 exit_arg=0. +;; 3) if RUNVM fails with exception - only one element is returned - exit arg (not to be mistaken with exit_code) +;; 4) in case of OOG, exit_code = -14 and exit_arg is amount of gas + +;; RunVM inputs: x_1 ... x_n n code [r] [c4] [c7] [g_l] [g_m] - x'_1 +;; RunVm outputs: x'_m exitcode [data'] [c4'] [c5] [g_c] + +;; the inputs have to be packed in a tuple +;; and the number of outputs is set by the nOutputs +tuple safePackedInputsRunVm( + tuple inputs, + int methodIdCRC, + int nOutputs, + slice code, + int gasLimit +) impure method_id asm """ + // STACK: left -> right: bottom -> top // + // Setup // STACK [ inputs, methodIdCRC, nOutputs, code, gasLimit ] ; len(callerStack) = 5 + // // STACK [ ..., inputs, methodIdCRC, nOutputs, code, nOutputs, gasLimit ] + DEPTH 5 PUSHINT SUB // STACK [ ..., inputs, methodIdCRC, nOutputs, code, gasLimit, len(callerStack) ] + 3 PUSHINT SETGLOBVAR // STACK [ ..., inputs, methodIdCRC, nOutputs, code, gasLimit ] + // Globvars[3] = len(...) + + // Arrange the stack for RUNVM + // nArgs is actually numberOfInputs + 1 + 2 PUSHINT // STACK [ ..., inputs, methodIdCRC, nOutputs, code, gasLimit, nArgs ] + s1 XCHG0 // STACK [ ..., inputs, methodIdCRC, nOutputs, code, nArgs, gasLimit ] + s1 s3 XCHG // STACK [ ..., inputs, methodIdCRC, nArgs, code, nOutputs, gasLimit ] + + // Run the given methodId in a sandbox child VM with flag 256 + 8 + 1 + // +1 : set c3 to code + // +8: take gas limit g_l from stack, return consumed gas g_c + // +256: pop integer r, return exactly r values from the top of the stack (only if exitcode=0 or 1; if not enough then exitcode=stack_underflow) + // here, r := nOutputs + + 265 RUNVM + // STACK [ ..., childStack] + // childStack is capped at nOutputs + + // Cleanup the return values + // 1. Figure out the length of the childStack + DEPTH // STACK [ ..., childStack, len(...) + len(childStack) ] + 3 PUSHINT GETGLOBVAR // STACK [ ..., childStack, len(...) + len(childStack), len(...) ] + // calculate len(childStack) = len(currentStack) - len(...) + SUB ABS // STACK [ ..., childStack, len(childStack) ] + + // 2. Turn the entire child stack into a tuple + // Tuplevar takes x1, ..., xn and n, and turns it into a tuple (x1, ..., xn) + // wouldn't know how many elements to take without the n + TUPLEVAR // STACK [ ..., tuple[*childStack] ] +"""; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/Attestation.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/Attestation.fc new file mode 100644 index 00000000..62e04df6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/Attestation.fc @@ -0,0 +1,48 @@ +#include "../../../../funC++/classlib.fc"; + +;; required storage name +const int lz::Attestation::NAME = "Attest"u; + +;; field names +const int lz::Attestation::hash = 0; ;; hash +const int lz::Attestation::confirmations = 1; ;; uint64 + +const int ULN_ATTESTATION_BITS = _HEADER_WIDTH + 256 + 64; +const int ULN_ATTESTATION_REFS = 0; +;; As long as the first 128 bits of the header match the below number, +;; the attestation is parseable by classlib +const int lz::Attestation::leading16Bytes = 0x417474657374815ED897BFFF; + +cell lz::Attestation::New(int hash, int confirmations) impure inline method_id { + return cl::declare( + lz::Attestation::NAME, + unsafeTuple([ + [cl::t::uint256, hash], ;; lz::Attestation::hash + [cl::t::uint64, confirmations] ;; lz::Attestation::confirmations + ]) + ); +} + +() lz::Attestation::validate(cell $self) impure inline method_id { + ;; low-level destructuring for efficiency + slice selfSlice = $self.begin_parse(); + (int bits, int refs) = selfSlice.slice_bits_refs(); + throw_unless( + cl::ERROR::MALFORMED_OBJECT, + (bits == ULN_ATTESTATION_BITS) + & (refs == ULN_ATTESTATION_REFS) + & (selfSlice~load_uint(128) == lz::Attestation::leading16Bytes) + ); +} + +const int lz::Attestation::_hashOffset = _HEADER_WIDTH; +const int lz::Attestation::_confirmationsOffset = lz::Attestation::_hashOffset + 256; + +;; (hash, confirmations) +(int, int) lz::Attestation::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint256At(lz::Attestation::_hashOffset), + selfSlice.preloadUint64At(lz::Attestation::_confirmationsOffset) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/DvnFeesPaidEvent.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/DvnFeesPaidEvent.fc new file mode 100644 index 00000000..9a713359 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/DvnFeesPaidEvent.fc @@ -0,0 +1,46 @@ +#include "../../../../funC++/classlib.fc"; + +;; required storage name +const int DvnFeesPaidEvent::NAME = "DvnFeePaid"u; + +const int DvnFeesPaidEvent::CONSTS::NULL_WORKER_ID = 0; + +;; field names +const int DvnFeesPaidEvent::requiredDVNs = 0; +const int DvnFeesPaidEvent::optionalDVNs = 1; +const int DvnFeesPaidEvent::serializedPayees = 2; + +cell DvnFeesPaidEvent::New( + cell requiredDVNs, + cell optionalDVNs, + cell serializedPayees +) impure inline method_id { + return cl::declare( + DvnFeesPaidEvent::NAME, + unsafeTuple([ + [cl::t::addressList, requiredDVNs], ;; DvnFeesPaidEvent::requiredDVNs + [cl::t::addressList, optionalDVNs], ;; DvnFeesPaidEvent::optionalDVNs + [cl::t::objRef, serializedPayees] ;; DvnFeesPaidEvent::serializedPayees + ]) + ); +} + +;; ========================= Object Builders ========================= + +const int DvnFeesPaidEvent::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 3); +const int DvnFeesPaidEvent::_headerFillerBits = _HEADER_WIDTH - DvnFeesPaidEvent::_headerInfoBits; +const int DvnFeesPaidEvent::_headerInfo = 5824155608645736562295029517863768977404; + +cell DvnFeesPaidEvent::build(cell requiredDVNs, cell optionalDVNs, cell serializedPayees) impure inline { + return begin_cell() + .store_uint(DvnFeesPaidEvent::_headerInfo, DvnFeesPaidEvent::_headerInfoBits) ;; header info + .store_ones(DvnFeesPaidEvent::_headerFillerBits) ;; header filler + .store_ref(requiredDVNs) ;; refs[0] + .store_ref(optionalDVNs) ;; refs[1] + .store_ref( + begin_cell() + .store_ref(serializedPayees) ;; refs[2] + .end_cell() + ) + .end_cell(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/ExecutorFeePaidEvent.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/ExecutorFeePaidEvent.fc new file mode 100644 index 00000000..d48e858c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/ExecutorFeePaidEvent.fc @@ -0,0 +1,38 @@ +#include "../../../../funC++/classlib.fc"; + +;; required storage name +const int ExecutorFeePaidEvent::NAME = "ExcFeePaid"u; + +const int ExecutorFeePaidEvent::CONSTS::NULL_WORKER_ID = 0; + +;; field names +const int ExecutorFeePaidEvent::executorAddress = 0; +const int ExecutorFeePaidEvent::feePaid = 1; + +cell ExecutorFeePaidEvent::New( + int executorAddress, + int feePaid +) impure inline method_id { + return cl::declare( + ExecutorFeePaidEvent::NAME, + unsafeTuple([ + [cl::t::address, executorAddress], ;; ExecutorFeePaidEvent::executorAddress + [cl::t::coins, feePaid] ;; ExecutorFeePaidEvent::feePaid + ]) + ); +} + +;; ========================= Object Builders ========================= + +const int ExecutorFeePaidEvent::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 2); +const int ExecutorFeePaidEvent::_headerFillerBits = _HEADER_WIDTH - ExecutorFeePaidEvent::_headerInfoBits; +const int ExecutorFeePaidEvent::_headerInfo = 22544389860372553502354985441937787; + +cell ExecutorFeePaidEvent::build(int executorAddress, int feePaid) impure inline { + return begin_cell() + .store_uint(ExecutorFeePaidEvent::_headerInfo, ExecutorFeePaidEvent::_headerInfoBits) ;; header info + .store_ones(ExecutorFeePaidEvent::_headerFillerBits) ;; header filler + .store_uint256(executorAddress) + .store_uint128(feePaid) + .end_cell(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUln.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUln.fc new file mode 100644 index 00000000..48256e96 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUln.fc @@ -0,0 +1,25 @@ +#include "../../../../funC++/classlib.fc"; + +;; required md name +const int md::InitUln::NAME = "initUln"u; + +;; field names +const int md::InitUln::connectionCode = 0; +const int md::InitUln::treasuryFeeBps = 1; + +cell md::InitUln::New(cell connectionCode, int treasuryFeeBps) impure inline method_id { + return cl::declare( + md::InitUln::NAME, + unsafeTuple([ + [cl::t::cellRef, connectionCode], ;; md::InitUln::connectionCode + [cl::t::uint16, treasuryFeeBps] ;; md::InitUln::treasuryFeeBps + ]) + ); +} + +cell md::InitUln::sanitize(cell $initUln) impure { + cell $connectionCode = $initUln.cl::get(md::InitUln::connectionCode); + int treasuryFeeBps = $initUln.cl::get(md::InitUln::treasuryFeeBps); + + return md::InitUln::New($connectionCode, treasuryFeeBps); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUlnConnection.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUlnConnection.fc new file mode 100644 index 00000000..131e35f1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUlnConnection.fc @@ -0,0 +1,62 @@ +#include "../../../../funC++/classlib.fc"; + +#include "UlnSendConfig.fc"; +#include "UlnReceiveConfig.fc"; + +;; required storage name +const int md::InitUlnConnection::NAME = "initUlnCon"u; + +;; field names +const int md::InitUlnConnection::ulnSendConfigOApp = 0; +const int md::InitUlnConnection::ulnReceiveConfigOApp = 1; +const int md::InitUlnConnection::endpointAddress = 2; +const int md::InitUlnConnection::channelAddress = 3; + +cell md::InitUlnConnection::New( + cell $ulnSendConfigOApp, + cell $ulnReceiveConfigOApp, + int endpointAddress, + int channelAddress +) impure inline method_id { + return cl::declare( + md::InitUlnConnection::NAME, + unsafeTuple([ + [cl::t::objRef, $ulnSendConfigOApp], ;; md::InitUlnConnection::ulnSendConfigOApp + [cl::t::objRef, $ulnReceiveConfigOApp], ;; md::InitUlnConnection::ulnReceiveConfigOApp + [cl::t::address, endpointAddress], ;; md::InitUlnConnection::endpointAddress + [cl::t::address, channelAddress] ;; md::InitUlnConnection::channelAddress + ]) + ); +} + +cell md::InitUlnConnection::NewOnlyConfig( + cell $ulnSendConfigOApp, + cell $ulnReceiveConfigOApp +) impure inline method_id { + return md::InitUlnConnection::New( + $ulnSendConfigOApp, + $ulnReceiveConfigOApp, + NULLADDRESS, + NULLADDRESS + ); +} + +;; Ensure the Uln receive config does not contain garbage bits etc. that would cause +;; undefined behaviors in the protocol +cell md::InitUlnConnection::sanitize(cell $initUlnConnection) impure { + cell $ulnSendConfigOApp = $initUlnConnection + .cl::get(md::InitUlnConnection::ulnSendConfigOApp); + cell $ulnReceiveConfigOApp = $initUlnConnection + .cl::get(md::InitUlnConnection::ulnReceiveConfigOApp); + int endpointAddress = $initUlnConnection + .cl::get
(md::InitUlnConnection::endpointAddress); + int channelAddress = $initUlnConnection + .cl::get
(md::InitUlnConnection::channelAddress); + + return md::InitUlnConnection::New( + UlnSendConfig::sanitize($ulnSendConfigOApp), + UlnReceiveConfig::sanitize($ulnReceiveConfigOApp), + endpointAddress, + channelAddress + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUlnManager.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUlnManager.fc new file mode 100644 index 00000000..30d1df82 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/InitUlnManager.fc @@ -0,0 +1,18 @@ +#include "../../../../funC++/classlib.fc"; + +;; required md name +const int md::InitUlnManager::NAME = "InitUlnMgr"u; + +;; field names +const int md::InitUlnManager::endpointCode = 0; +const int md::InitUlnManager::channelCode = 1; + +cell md::InitUlnManager::New(cell endpointCode, cell channelCode) impure inline method_id { + return cl::declare( + md::InitUlnManager::NAME, + unsafeTuple([ + [cl::t::cellRef, endpointCode], ;; md::InitUlnManager::endpointCode + [cl::t::cellRef, channelCode] ;; md::InitUlnManager::channelCode + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/RentRefill.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/RentRefill.fc new file mode 100644 index 00000000..02645618 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/RentRefill.fc @@ -0,0 +1,18 @@ +#include "../../../../funC++/classlib.fc"; + +;; required md name +const int md::RentRefill::NAME = "RentRefill"u; + +;; field names +const int md::RentRefill::address = 0; +const int md::RentRefill::amount = 1; + +cell md::RentRefill::New(int address, int amount) impure inline method_id { + return cl::declare( + md::RentRefill::NAME, + unsafeTuple([ + [cl::t::address, address], ;; md::RentRefill::address + [cl::t::coins, amount] ;; md::RentRefill::amount + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/SetAdminWorkerAddresses.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/SetAdminWorkerAddresses.fc new file mode 100644 index 00000000..6f45a1d6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/SetAdminWorkerAddresses.fc @@ -0,0 +1,24 @@ +#include "../../../../funC++/classlib.fc"; + +;; required md name +const int md::SetAdminWorkerAddresses::NAME = "adminwork"u; + +;; field names + +;; AddressList +const int md::SetAdminWorkerAddresses::adminWorkers = 0; + +cell md::SetAdminWorkerAddresses::New(cell adminWorkers) impure inline method_id { + return cl::declare( + md::SetAdminWorkerAddresses::NAME, + unsafeTuple([ + [cl::t::addressList, adminWorkers] ;; md::SetAdminWorkerAddresses::adminWorkers + ]) + ); +} + +cell md::SetAdminWorkerAddresses::sanitize(cell $setAdminWorkerAddresses) impure inline_ref { + cell adminWorkers = $setAdminWorkerAddresses.cl::get(md::SetAdminWorkerAddresses::adminWorkers); + + return md::SetAdminWorkerAddresses::New(adminWorkers); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/TreasuryFeeBps.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/TreasuryFeeBps.fc new file mode 100644 index 00000000..d2d43169 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/TreasuryFeeBps.fc @@ -0,0 +1,16 @@ +#include "../../../../funC++/classlib.fc"; + +;; required md name +const int md::TreasuryFeeBps::NAME = "tfeebps"u; + +;; field names +const int md::TreasuryFeeBps::treasuryFeeBps = 0; + +cell md::TreasuryFeeBps::New(int treasuryFeeBps) impure inline method_id { + return cl::declare( + md::TreasuryFeeBps::NAME, + unsafeTuple([ + [cl::t::uint16, treasuryFeeBps] ;; md::TreasuryFeeBps::treasuryFeeBps + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnEvents.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnEvents.fc new file mode 100644 index 00000000..0a6f0a4b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnEvents.fc @@ -0,0 +1,46 @@ +#include "../../../../funC++/classlib.fc"; + +;; required storage name +const int UlnEvents::NAME = "UlnEvents"u; + +const int UlnEvents::CONSTS::NULL_WORKER_ID = 0; + +;; field names +const int UlnEvents::workerEvents = 0; +const int UlnEvents::dvnFeesPaidEvent = 1; +const int UlnEvents::executorFeePaidEvent = 2; + +cell UlnEvents::New( + cell workerEvents, + cell dvnFeesPaidEvent, + cell executorFeePaidEvent +) impure inline method_id { + return cl::declare( + UlnEvents::NAME, + unsafeTuple([ + [cl::t::objRef, workerEvents], ;; UlnEvents::workerEvents + [cl::t::objRef, dvnFeesPaidEvent], ;; UlnEvents::dvnFeesPaidEvent + [cl::t::objRef, executorFeePaidEvent] ;; UlnEvents::executorFeePaidEvent + ]) + ); +} + +;; ========================= Object Builders ========================= + +const int UlnEvents::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 3); +const int UlnEvents::_headerFillerBits = _HEADER_WIDTH - UlnEvents::_headerInfoBits; +const int UlnEvents::_headerInfo = 28386846067720270005963581604331155452; + +cell UlnEvents::build(cell workerEvents, cell dvnFeesPaidEvent, cell executorFeePaidEvent) impure inline { + return begin_cell() + .store_uint(UlnEvents::_headerInfo, UlnEvents::_headerInfoBits) ;; header info + .store_ones(UlnEvents::_headerFillerBits) ;; header filler + .store_ref(workerEvents) ;; refs[0] + .store_ref(dvnFeesPaidEvent) ;; refs[1] + .store_ref( + begin_cell() + .store_ref(executorFeePaidEvent) ;; refs[2] + .end_cell() + ) + .end_cell(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnReceiveConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnReceiveConfig.fc new file mode 100644 index 00000000..afeeb93a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnReceiveConfig.fc @@ -0,0 +1,335 @@ +#include "../../../../funC++/classlib.fc"; + +#include "../../../../funC++/dataStructures/AddressList.fc"; + +const int UlnReceiveConfig::CONST::NIL_ADDRESS = MAX_U256; + +const int UlnReceiveConfig::COMMIT_PACKET_INTERNAL_MESSAGE_COUNT = 4; +;; connection -> endpoint -> channel -> connection +const int UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR = 321; + +;; Maximum number of DVNs allowed in the config is 8 required + 8 optional = 16 total. +;; The hashlookups is a mapping of nonce -> address -> attestation +;; meaning that 16 DVNs will require (2 * 16) - 1 cells for the dictionary tree inner nodes +;; and 16 cells for the attestations = 63 cells per nonce. +;; We allow up to 1000 nonces in the hashlookups, so the maximum number of cells required +;; for the hashlookups is 1000 * 63 = 63000 cells. +;; This leaves 2535 cells for the fixed-size storage and bytecode of this contract, which is +;; sufficient. +const int UlnReceiveConfig::MaxRequiredDVNs = 8; +const int UlnReceiveConfig::MaxOptionalDVNs = 4; + +;; required storage name +const int UlnReceiveConfig::NAME = "UlnRecvCfg"u; + +;; field names + +;; EVM struct ExecutorConfig +;; uint32 maxMessageSize; +;; address executor; + +const int UlnReceiveConfig::minCommitPacketGasNull = 0; ;; bool +const int UlnReceiveConfig::minCommitPacketGas = 1; ;; uint32 +const int UlnReceiveConfig::confirmationsNull = 2; ;; bool +const int UlnReceiveConfig::confirmations = 3; ;; uint64 +const int UlnReceiveConfig::requiredDVNsNull = 4; ;; bool +const int UlnReceiveConfig::requiredDVNs = 5; ;; address[] will be stored as a dictionary +const int UlnReceiveConfig::optionalDVNsNull = 6; ;; bool +const int UlnReceiveConfig::optionalDVNs = 7; ;; address[] will be stored as a dictionary +const int UlnReceiveConfig::optionalDVNThreshold = 8; ;; uint8 + +const int UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LARGE = 385; +const int UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LOW = 386; +const int UlnReceiveConfig::ERROR::DVN_COUNTS_ALL_NIL = 387; +const int UlnReceiveConfig::ERROR::INVALID_MIN_COMMIT_PACKET_GAS = 388; + +;; EVM struct UlnReceiveConfig +;; uint64 confirmations; +;; // we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gas +;; uint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) +;; uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) +;; uint8 optionalDVNThreshold; // (0, optionalDVNCount] +;; address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNs +;; address[] optionalDVNs; // no duplicates. sorted an an ascending order. allowed overlap with requiredDVNs + +cell UlnReceiveConfig::New( + int minCommitPacketGasNull, + int minCommitPacketGas, + int confirmationsNull, + int confirmations, + int requiredDVNsNull, + cell requiredDVNs, + int optionalDVNsNull, + cell optionalDVNs, + int optionalDVNThreshold +) impure method_id { + return cl::declare( + UlnReceiveConfig::NAME, + unsafeTuple([ + [cl::t::bool, minCommitPacketGasNull], ;; UlnReceiveConfig::minCommitPacketGasNull + [cl::t::uint32, minCommitPacketGas], ;; UlnReceiveConfig::minCommitPacketGas + [cl::t::bool, confirmationsNull], ;; UlnReceiveConfig::confirmationsNull + [cl::t::uint64, confirmations], ;; UlnReceiveConfig::confirmations + [cl::t::bool, requiredDVNsNull], ;; UlnReceiveConfig::requiredDVNsNull + [cl::t::addressList, requiredDVNs], ;; UlnReceiveConfig::requiredDVNs + [cl::t::bool, optionalDVNsNull], ;; UlnReceiveConfig::optionalDVNsNull + [cl::t::addressList, optionalDVNs], ;; UlnReceiveConfig::requiredDVNs + [cl::t::uint8, optionalDVNThreshold] ;; UlnReceiveConfig::optionalDVNThreshold + ]) + ); +} + +;; Important assumption: the gas consumption of the endpoint will not increase past +;; 1 million gas per handler in any future update of the TVM. +int UlnReceiveConfig::MaxCommitPacketValueAssertion() impure method_id { + (_, _, _, _, int gasLimit, _, _, _, _) = parseGasLimitsPrices(BASECHAIN); + gasLimit = max(gasLimit, 1000000); + + return ( + _gasToNanoton(gasLimit * UlnReceiveConfig::COMMIT_PACKET_INTERNAL_MESSAGE_COUNT) + + ( + UlnReceiveConfig::COMMIT_PACKET_INTERNAL_MESSAGE_COUNT + * get_forward_fee(false, MAX_U8 * MAX_CELL_BITS, MAX_U8) + ) + ); +} + +cell UlnReceiveConfig::NewWithDefaults() inline method_id { + return UlnReceiveConfig::New( + true, + 0, + true, + 0, + true, + AddressList::empty(), + true, + AddressList::empty(), + 0 + ); +} + +;; ========================== Object getters =========================== + +const int UlnReceiveConfig::_minCommitPacketGasNullOffset = _HEADER_WIDTH; +const int UlnReceiveConfig::_minCommitPacketGasOffset = UlnReceiveConfig::_minCommitPacketGasNullOffset + 1; +const int UlnReceiveConfig::_confirmationsNullOffset = UlnReceiveConfig::_minCommitPacketGasOffset + 32; +const int UlnReceiveConfig::_confirmationsOffset = UlnReceiveConfig::_confirmationsNullOffset + 1; +const int UlnReceiveConfig::_requiredDVNsNullOffset = UlnReceiveConfig::_confirmationsOffset + 64; +const int UlnReceiveConfig::_optionalDVNsNullOffset = UlnReceiveConfig::_requiredDVNsNullOffset + 1; +const int UlnReceiveConfig::_optionalDVNThresholdOffset = UlnReceiveConfig::_optionalDVNsNullOffset + 1; + + +int UlnReceiveConfig::getMinCommitPacketGas(cell $self) impure inline { + return $self.cellPreloadUint32At(UlnReceiveConfig::_minCommitPacketGasOffset); +} + +int UlnReceiveConfig::getConfirmationsNull(cell $self) impure inline { + return $self.cellPreloadBoolAt(UlnReceiveConfig::_confirmationsNullOffset); +} + +int UlnReceiveConfig::getConfirmations(cell $self) impure inline { + return $self.cellPreloadUint64At(UlnReceiveConfig::_confirmationsOffset); +} + +int UlnReceiveConfig::getOptionalDVNThreshold(cell $self) impure inline { + return $self.cellPreloadUint8At(UlnReceiveConfig::_optionalDVNThresholdOffset); +} + +;; ========================== Object multi - getters ====================== + +(int, cell) UlnReceiveConfig::getRequiredDVNsAndNull(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadBoolAt(UlnReceiveConfig::_requiredDVNsNullOffset), + selfSlice.preloadRefAt(0) + ); +} + +(int, cell) UlnReceiveConfig::getOptionalDVNsAndNull(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadBoolAt(UlnReceiveConfig::_optionalDVNsNullOffset), + selfSlice.preloadRefAt(1) + ); +} + +;; returns (requiredDVNsNull, requiredDVNs, optionalDVNsNull, optionalDVNs) +(int, cell, int, cell) UlnReceiveConfig::getRequiredAndOptionalDVNsAndNull(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadBoolAt(UlnReceiveConfig::_requiredDVNsNullOffset), + selfSlice.preloadRefAt(0), + selfSlice.preloadBoolAt(UlnReceiveConfig::_optionalDVNsNullOffset), + selfSlice.preloadRefAt(1) + ); +} + +;; (minCommitPacketGasNull, minCommitPacketGas, OptionalDvnNull, optionalDvnThreshold) +(int, int, int, int) UlnReceiveConfig::getCommitInfo(cell $self) impure inline { + slice $selfSlice = $self.begin_parse(); + ;; Could save a bit of gas by using self-modifying parse but + ;; it makes the code a lot less readable + return ( + $selfSlice.preloadBoolAt(UlnReceiveConfig::_minCommitPacketGasNullOffset), + $selfSlice.preloadUint32At(UlnReceiveConfig::_minCommitPacketGasOffset), + $selfSlice.preloadBoolAt(UlnReceiveConfig::_optionalDVNsNullOffset), + $selfSlice.preloadUint8At(UlnReceiveConfig::_optionalDVNThresholdOffset) + ); +} + +;; ============== Object Utils ============== + +int UlnReceiveConfig::utils::resolveConfirmations( + cell $UlnReceiveConfig, + cell $defaultUlnReceiveConfig +) impure inline { + if ($UlnReceiveConfig.UlnReceiveConfig::getConfirmationsNull()) { + return $defaultUlnReceiveConfig.UlnReceiveConfig::getConfirmations(); + } + return $UlnReceiveConfig.UlnReceiveConfig::getConfirmations(); +} + +;; (requiredDVNs, optionalDVNs, isValid) +(cell, cell, int) UlnReceiveConfig::utils::getVerifyConfig( + cell $customUlnReceiveConfig, + cell $defaultUlnReceiveConfig +) impure inline { + int isValid = true; + + ( + int customRequiredDVNsNull, + cell requiredDVNs, + int customOptionalDVNsNull, + cell optionalDVNs + ) = UlnReceiveConfig::getRequiredAndOptionalDVNsAndNull($customUlnReceiveConfig); + + if (customRequiredDVNsNull) { + ( + int defaultRequiredDVNsNull, + requiredDVNs + ) = UlnReceiveConfig::getRequiredDVNsAndNull($defaultUlnReceiveConfig); + isValid &= (defaultRequiredDVNsNull == false); + } + + if (customOptionalDVNsNull) { + ( + int defaultOptionalDVNsNull, + optionalDVNs + ) = UlnReceiveConfig::getOptionalDVNsAndNull($defaultUlnReceiveConfig); + isValid &= (defaultOptionalDVNsNull == false); + } + + return (requiredDVNs, optionalDVNs, isValid); +} + +;; (requiredDVNs, optionalDVNs, optionalDVNThreshold, confirmations, commitPacketGas, isValid) +(cell, cell, int, int, int, int) UlnReceiveConfig::utils::getCommitConfig( + cell $customUlnReceiveConfig, + cell $defaultUlnReceiveConfig +) impure inline method_id { + ( + cell requiredDVNs, + cell optionalDVNs, + int isValid + ) = UlnReceiveConfig::utils::getVerifyConfig( + $customUlnReceiveConfig, + $defaultUlnReceiveConfig + ); + + ( + int minCommitPacketGasNull, + int commitPacketGas, + int optionalDVNsNull, + int optionalDVNThreshold + ) = UlnReceiveConfig::getCommitInfo($customUlnReceiveConfig); + + if (optionalDVNsNull) { + optionalDVNThreshold = $defaultUlnReceiveConfig.UlnReceiveConfig::getOptionalDVNThreshold(); + } + + if (minCommitPacketGasNull) { + commitPacketGas = $defaultUlnReceiveConfig.UlnReceiveConfig::getMinCommitPacketGas(); + } + + int confirmations = UlnReceiveConfig::utils::resolveConfirmations( + $customUlnReceiveConfig, + $defaultUlnReceiveConfig + ); + + return (requiredDVNs, optionalDVNs, optionalDVNThreshold, confirmations, commitPacketGas, isValid); +} + +;; Ensure the Uln receive config does not contain garbage bits etc. that would cause +;; undefined behaviors in the protocol +cell UlnReceiveConfig::sanitize(cell $ulnReceiveConfigOApp) impure { + int useDefaultMinCommitPacketGas = + $ulnReceiveConfigOApp.cl::get(UlnReceiveConfig::minCommitPacketGasNull); + int minCommitPacketGas = + $ulnReceiveConfigOApp.cl::get(UlnReceiveConfig::minCommitPacketGas); + int useDefaultConfirmations = + $ulnReceiveConfigOApp.cl::get(UlnReceiveConfig::confirmationsNull); + int confirmations = + $ulnReceiveConfigOApp.cl::get(UlnReceiveConfig::confirmations); + int useDefaultRequiredDVNs = + $ulnReceiveConfigOApp.cl::get(UlnReceiveConfig::requiredDVNsNull); + cell requiredDVNs = + $ulnReceiveConfigOApp.cl::get(UlnReceiveConfig::requiredDVNs); + int useDefaultOptionalDVNs = + $ulnReceiveConfigOApp.cl::get(UlnReceiveConfig::optionalDVNsNull); + cell optionalDVNs = + $ulnReceiveConfigOApp.cl::get(UlnReceiveConfig::optionalDVNs); + int optionalDVNThreshold = + $ulnReceiveConfigOApp.cl::get(UlnReceiveConfig::optionalDVNThreshold); + + int numRequiredDVNs = requiredDVNs.AddressList::length(); + int numOptionalDVNs = optionalDVNs.AddressList::length(); + + throw_unless( + UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR, + AddressList::isValid(requiredDVNs, UlnReceiveConfig::MaxRequiredDVNs) + & AddressList::isValid(optionalDVNs, UlnReceiveConfig::MaxOptionalDVNs) + ); + + throw_if( + UlnReceiveConfig::ERROR::INVALID_MIN_COMMIT_PACKET_GAS, + (useDefaultMinCommitPacketGas == false) + & (minCommitPacketGas <= 0) + ); + + ;; Prevent a malicious OApp from setting the threshold to a value that would + ;; make it impossible to commit a packet + throw_if( + UlnReceiveConfig::ERROR::INVALID_MIN_COMMIT_PACKET_GAS, + _gasToNanoton(minCommitPacketGas) > UlnReceiveConfig::MaxCommitPacketValueAssertion() + ); + + throw_if( + UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LARGE, + optionalDVNThreshold > numOptionalDVNs + ); + + throw_if( + UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LOW, + (numOptionalDVNs > 0) & (optionalDVNThreshold <= 0) + ); + + throw_if( + UlnReceiveConfig::ERROR::DVN_COUNTS_ALL_NIL, + (useDefaultRequiredDVNs == false) + & (useDefaultOptionalDVNs == false) + & (numRequiredDVNs == 0) + & (numOptionalDVNs == 0) + ); + + return UlnReceiveConfig::New( + useDefaultMinCommitPacketGas, + minCommitPacketGas, + useDefaultConfirmations, + confirmations, + useDefaultRequiredDVNs, + requiredDVNs, + useDefaultOptionalDVNs, + optionalDVNs, + optionalDVNThreshold + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnSend.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnSend.fc new file mode 100644 index 00000000..e0d238b9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnSend.fc @@ -0,0 +1,77 @@ +#include "../../../../funC++/classlib.fc"; + +;; required storage name +const int md::UlnSend::NAME = "UlnSend"u; + +;; field names +const int md::UlnSend::lzSend = 0; +const int md::UlnSend::customUlnSendConfig = 1; +const int md::UlnSend::connectionInitialStorage = 2; +const int md::UlnSend::forwardingAddress = 3; + +cell md::UlnSend::New( + cell $lzSend, + cell $customUlnSendConfig, + cell $connectionInitialStorage, + int forwardingAddress +) impure inline method_id { + return cl::declare( + md::UlnSend::NAME, + unsafeTuple([ + [cl::t::objRef, $lzSend], ;; md::UlnSend::lzSend + [cl::t::objRef, $customUlnSendConfig], ;; md::UlnSend::customUlnSendConfig + [cl::t::objRef, $connectionInitialStorage], ;; md::UlnSend::connectionInitialStorage + [cl::t::address, forwardingAddress] ;; md::UlnSend::forwardingAddress + ]) + ); +} + +;; ==================================== Object Builders ============================= + +const int md::UlnSend::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 4); +const int md::UlnSend::_headerFillerBits = _HEADER_WIDTH - md::UlnSend::_headerInfoBits; +const int md::UlnSend::_headerInfo = 113547385374824943747209776028946924923; + +cell md::UlnSend::build( + cell $lzSend, + cell $customUlnSendConfig, + cell $connectionInitialStorage, + int forwardingAddress +) impure inline method_id { + return begin_cell() + .store_uint(md::UlnSend::_headerInfo, md::UlnSend::_headerInfoBits) ;; header info + .store_ones(md::UlnSend::_headerFillerBits) ;; header filler + .store_uint256(forwardingAddress) ;; numerical field + .store_ref($lzSend) ;; refs[0] + .store_ref($customUlnSendConfig) ;; refs[1] + .store_ref( + begin_cell() + .store_ref($connectionInitialStorage) ;; refs[2] + .end_cell() + ) + .end_cell(); +} + + +;; ==================================== Object Getters ============================= + +const int md::UlnSend::_forwardingAddressOffset = _HEADER_WIDTH; + +cell md::UlnSend::getConnectionInitialStorage(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(0); +} + +int md::UlnSend::getForwardingAddress(cell $self) impure inline { + return $self.cellPreloadAddressAt(md::UlnSend::_forwardingAddressOffset); +} + +;; ==================================== Object Multi-Getters ============================= + +(cell, cell) md::UlnSend::getLzSendAndCustomUlnSendConfig(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice~load_ref(), + selfSlice~load_ref() + ); +} + diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnSendConfig.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnSendConfig.fc new file mode 100644 index 00000000..9d806349 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnSendConfig.fc @@ -0,0 +1,169 @@ +#include "../../../../funC++/classlib.fc"; + +#include "../../../../funC++/dataStructures/AddressList.fc"; + +const int MAX_DVN_COUNT = 255; ;; uint8 +const int lz::UlnSendConfig::CONST::NIL_ADDRESS = MAX_U256; + +const int UlnSendConfig::ERROR::DVN_CONFIG_ERROR = 353; + +;; required storage name +const int lz::UlnSendConfig::NAME = "UlnSendCfg"u; + +;; field names + +;; EVM struct ExecutorConfig +;; uint32 maxMessageSize; +;; address executor; + +;; Both side configs +const int UlnSendConfig::workerQuoteGasLimit = 0; ;; uint64, set to 0 for using default +const int UlnSendConfig::maxMessageBytes = 1; ;; uint32, set to 0 for using default +const int UlnSendConfig::executorNull = 2; ;; bool +const int UlnSendConfig::executor = 3; ;; address (set to NIL for using default) +const int UlnSendConfig::requiredDVNsNull = 4; ;; bool +const int UlnSendConfig::requiredDVNs = 5; ;; address[] will be stored as a dictionary +const int UlnSendConfig::optionalDVNsNull = 6; ;; bool +const int UlnSendConfig::optionalDVNs = 7; ;; address[] will be stored as a dictionary +const int UlnSendConfig::confirmationsNull = 8; +const int UlnSendConfig::confirmations = 9; + +;; these null flags above mean "try to resolve this field from the other config" if they're set to true. + +cell UlnSendConfig::New( + int workerQuoteGasLimit, + int maxMessageBytes, + int executorNull, + int executor, + int requiredDVNsNull, + cell requiredDVNs, + int optionalDVNsNull, + cell optionalDVNs, + int confirmationsNull, + int confirmations +) impure method_id { + return cl::declare( + lz::UlnSendConfig::NAME, + unsafeTuple([ + [cl::t::uint32, workerQuoteGasLimit], ;; lz::UlnSendConfig::workerQuoteGasLimit + [cl::t::uint32, maxMessageBytes], ;; lz::UlnSendConfig::maxMessageBytes + [cl::t::bool, executorNull], ;; lz::UlnSendConfig::executorNull + [cl::t::address, executor], ;; lz::UlnSendConfig::executor + [cl::t::bool, requiredDVNsNull], ;; lz::UlnSendConfig::requiredDVNsNull + [cl::t::addressList, requiredDVNs], ;; lz::UlnSendConfig::requiredDVNs + [cl::t::bool, optionalDVNsNull], ;; lz::UlnSendConfig::optionalDVNsNull + [cl::t::addressList, optionalDVNs], ;; lz::UlnSendConfig::optionalDVNs + [cl::t::bool, confirmationsNull], ;; lz::UlnSendConfig::confirmationsNull + [cl::t::uint64, confirmations] ;; lz::UlnSendConfig::confirmations + ]) + ); +} + +cell UlnSendConfig::NewWithDefaults() impure method_id { + return UlnSendConfig::New( + 0, ;; Default worker quote gas limit + 0, ;; Default max message bytes + true, + NULLADDRESS, + true, + empty_cell(), + true, + empty_cell(), + true, + 0 ;; Default confirmations + ); +} + +;; quoteGasLimit, maxMsgBytes, executor, requiredDVNs, optionalDVNs, confirmations, isValid +(int, int, int, cell, cell, int, int) lz::UlnSendConfig::resolveUlnSendConfig( + cell $customUlnSendConfig, + cell $defaultUlnSendConfig +) impure inline { + int isValid = true; + + ;; low-level destructuring of customUlnSendConfig for efficiency + ;; 1. skip the header + slice customUlnSendConfigSlice = $customUlnSendConfig + .begin_parse() + .sskipfirst(_HEADER_WIDTH, 0); + + ;; read all fields + ;; 32 + 32 + 1 + 256 + 1 + 1 + 1 + 64 = 388 bits of data + 2 refs + ;; all fit in the root cell with +350 bits for header (388 + 350 = 738 < 1023) + int quoteGasLimit = customUlnSendConfigSlice~load_uint32(); + int maxMsgBytes = customUlnSendConfigSlice~load_uint32(); + int executorNull = customUlnSendConfigSlice~load_bool(); + int executor = customUlnSendConfigSlice~load_uint256(); + int requiredDVNsNull = customUlnSendConfigSlice~load_bool(); + cell requiredDVNs = customUlnSendConfigSlice~load_ref(); + int optionalDVNsNull = customUlnSendConfigSlice~load_bool(); + cell optionalDVNs = customUlnSendConfigSlice~load_ref(); + int confirmationsNull = customUlnSendConfigSlice~load_bool(); + int confirmations = customUlnSendConfigSlice~load_uint64(); + + if (quoteGasLimit == 0) { + quoteGasLimit = $defaultUlnSendConfig + .cl::get(UlnSendConfig::workerQuoteGasLimit); + } + + if (maxMsgBytes == 0) { + maxMsgBytes = $defaultUlnSendConfig + .cl::get(UlnSendConfig::maxMessageBytes); + } + + if (executorNull) { + isValid &= ($defaultUlnSendConfig.cl::get(UlnSendConfig::executorNull) == false); + executor = $defaultUlnSendConfig.cl::get
(UlnSendConfig::executor); + } + + if (requiredDVNsNull) { + isValid &= ($defaultUlnSendConfig.cl::get(UlnSendConfig::requiredDVNsNull) == false); + requiredDVNs = $defaultUlnSendConfig.cl::get(UlnSendConfig::requiredDVNs); + } + + if (optionalDVNsNull) { + isValid &= ($defaultUlnSendConfig.cl::get(UlnSendConfig::optionalDVNsNull) == false); + optionalDVNs = $defaultUlnSendConfig.cl::get(UlnSendConfig::optionalDVNs); + } + + if (confirmationsNull) { + isValid &= ($defaultUlnSendConfig.cl::get(UlnSendConfig::confirmationsNull) == false); + confirmations = $defaultUlnSendConfig.cl::get(UlnSendConfig::confirmations); + } + + return (quoteGasLimit, maxMsgBytes, executor, requiredDVNs, optionalDVNs, confirmations, isValid); +} + +;; Ensure the Uln send config does not contain garbage bits etc. that would cause +;; undefined behaviors in the protocol +cell UlnSendConfig::sanitize(cell $ulnSendConfig) impure { + int workerQuoteGasLimit = $ulnSendConfig.cl::get(UlnSendConfig::workerQuoteGasLimit); + int maxMessageBytes = $ulnSendConfig.cl::get(UlnSendConfig::maxMessageBytes); + int useDefaultExecutor = $ulnSendConfig.cl::get(UlnSendConfig::executorNull); + int executor = $ulnSendConfig.cl::get
(UlnSendConfig::executor); + int useDefaultRequiredDVNs = $ulnSendConfig.cl::get(UlnSendConfig::requiredDVNsNull); + cell requiredDVNs = $ulnSendConfig.cl::get(UlnSendConfig::requiredDVNs); + int useDefaultOptionalDVNs = $ulnSendConfig.cl::get(UlnSendConfig::optionalDVNsNull); + cell optionalDVNs = $ulnSendConfig.cl::get(UlnSendConfig::optionalDVNs); + int useDefaultConfirmations = $ulnSendConfig.cl::get(UlnSendConfig::confirmationsNull); + int confirmations = $ulnSendConfig.cl::get(UlnSendConfig::confirmations); + + throw_unless( + UlnSendConfig::ERROR::DVN_CONFIG_ERROR, + AddressList::isValid(requiredDVNs, MAX_DVN_COUNT) + & AddressList::isValid(optionalDVNs, MAX_DVN_COUNT) + ); + + return UlnSendConfig::New( + workerQuoteGasLimit, + maxMessageBytes, + useDefaultExecutor, + executor, + useDefaultRequiredDVNs, + requiredDVNs, + useDefaultOptionalDVNs, + optionalDVNs, + useDefaultConfirmations, + confirmations + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnVerification.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnVerification.fc new file mode 100644 index 00000000..c744c630 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnVerification.fc @@ -0,0 +1,31 @@ +#include "Attestation.fc"; + +;; required storage name +const int md::UlnVerification::NAME = "UlnVerify"u; + +;; field names +const int md::UlnVerification::nonce = 0; ;; hash +const int md::UlnVerification::attestation = 1; ;; uint64 + +cell md::UlnVerification::New(int nonce, int hash, int confirmations) impure inline method_id { + return cl::declare( + md::UlnVerification::NAME, + unsafeTuple([ + [cl::t::uint64, nonce], ;; md::UlnVerification::nonce + [cl::t::objRef, lz::Attestation::New( + hash, + confirmations + )] ;; md::UlnVerification::attestation + ]) + ); +} + +;; ==================================== Object Getters ============================= +const int md::UlnVerification::_nonceOffset = _HEADER_WIDTH; + +(int, cell) md::UlnVerification::deserialize(cell $self) impure inline { + return ( + $self.cellPreloadUint64At(md::UlnVerification::_nonceOffset), + $self.cellPreloadRefAt(0) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibBytecode.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibBytecode.fc new file mode 100644 index 00000000..648e41c8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibBytecode.fc @@ -0,0 +1,22 @@ +#include "../../../../funC++/classlib.fc"; + +;; required storage name +const int md::UlnWorkerFeelibBytecode::NAME = "Ulnbytecod"u; + +;; field names +const int md::UlnWorkerFeelibBytecode::bytecode = 0; ;; cellRef + +cell md::UlnWorkerFeelibBytecode::New(cell bytecode) impure inline method_id { + return cl::declare( + md::UlnWorkerFeelibBytecode::NAME, + unsafeTuple([ + [cl::t::cellRef, bytecode] ;; md::UlnWorkerFeelibBytecode::nonce + ]) + ); +} + +cell md::UlnWorkerFeelibBytecode::sanitize(cell $ulnWorkerFeelibBytecode) impure inline_ref { + cell bytecode = $ulnWorkerFeelibBytecode.cl::get(md::UlnWorkerFeelibBytecode::bytecode); + + return md::UlnWorkerFeelibBytecode::New(bytecode); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibEvents.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibEvents.fc new file mode 100644 index 00000000..3d7c2708 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibEvents.fc @@ -0,0 +1,58 @@ +#include "../../../../funC++/classlib.fc"; + +;; required storage name +const int md::UlnWorkerFeelibEvents::NAME = "UlnWrkEvnt"u; + +;; field names +const int md::UlnWorkerFeelibEvents::workerAddress = 0; +const int md::UlnWorkerFeelibEvents::workerEvents = 1; +const int md::UlnWorkerFeelibEvents::nextWorkerEvents = 2; + +cell md::UlnWorkerFeelibEvents::New( + int workerAddress, + cell $workerEvents, + cell $nextWorkerEvents +) impure inline method_id { + return cl::declare( + md::UlnWorkerFeelibEvents::NAME, + unsafeTuple([ + [cl::t::address, workerAddress], ;; md::UlnWorkerFeelibEvents::workerAddress + [cl::t::objRef, $workerEvents], ;; md::UlnWorkerFeelibEvents::workerEvents + [cl::t::objRef, $nextWorkerEvents] ;; md::UlnWorkerFeelibEvents::nextWorkerEvents + ]) + ); +} + +tuple UlnWorkerFeelibEventsBuilder::create() impure inline { + return empty_tuple(); +} + +tuple UlnWorkerFeelibEventsBuilder::push( + tuple eventBuilder, + int workerAddress, + cell eventBody +) impure inline { + if (eventBody.is_null()) { + return eventBuilder; + } + return eventBuilder.tpush([workerAddress, eventBody]); +} + +cell UlnWorkerFeelibEvents::FromBuilder(tuple eventBuilder) impure inline { + if (eventBuilder.tlen() == 0) { + ;; short-circuit the common case for efficiency + return cl::nullObject(); + } + + cell $workerEvents = cl::nullObject(); + while (eventBuilder.tlen() > 0) { + [int workerAddress, cell $workerEventBody] = eventBuilder~tpop(); + $workerEvents = md::UlnWorkerFeelibEvents::New( + workerAddress, + $workerEventBody, + $workerEvents + ); + } + + return $workerEvents; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibInfo.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibInfo.fc new file mode 100644 index 00000000..c9dadcd4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibInfo.fc @@ -0,0 +1,106 @@ +#include "../../../../funC++/classlib.fc"; + +;; required storage name +const int UlnWorkerFeelibInfo::NAME = "UlnWrkInfo"u; + +;; 'UlnWorkerFeelibInfo::useDefaults' is ONLY used during the oapp config set flow. +;; When setting 'epConfigDefaults' as the owner, the flag doesnt matter, because call is for defaults + +;; field names +const int UlnWorkerFeelibInfo::workerAddress = 0; +const int UlnWorkerFeelibInfo::workerFeelibBytecode = 1; +const int UlnWorkerFeelibInfo::workerFeelibStorage = 2; +const int UlnWorkerFeelibInfo::friendWorkerAddress = 3; +const int UlnWorkerFeelibInfo::dstEid = 4; +const int UlnWorkerFeelibInfo::rentBalance = 5; +const int UlnWorkerFeelibInfo::lastRentTimestamp = 6; +const int UlnWorkerFeelibInfo::isAdmin = 7; + +cell UlnWorkerFeelibInfo::New( + int workerAddress, + cell workerFeelibBytecode, + cell workerFeelibStorage, + int friendWorkerAddress, + int dstEid, + int rentBalance, + int isAdmin +) impure inline method_id { + return cl::declare( + UlnWorkerFeelibInfo::NAME, + unsafeTuple([ + [cl::t::address, workerAddress], ;; UlnWorkerFeelibInfo::workerAddress + [cl::t::cellRef, workerFeelibBytecode], ;; UlnWorkerFeelibInfo::workerFeelibBytecode + [cl::t::objRef, workerFeelibStorage], ;; UlnWorkerFeelibInfo::workerFeelibStorage + [cl::t::address, friendWorkerAddress], ;; UlnWorkerFeelibInfo::friendWorkerAddress + [cl::t::uint32, dstEid], ;; UlnWorkerFeelibInfo::dstEid + [cl::t::coins, rentBalance], ;; UlnWorkerFeelibInfo::rentBalance + [cl::t::uint64, 0], ;; UlnWorkerFeelibInfo::lastRentTimestamp + [cl::t::bool, isAdmin] ;; UlnWorkerFeelibInfo::isAdmin + ]) + ); +} + +;; ==================================== Object Getters ============================= + +const int UlnWorkerFeelibInfo::_workerAddressOffset = _HEADER_WIDTH; +const int UlnWorkerFeelibInfo::_friendWorkerAddressOffset = UlnWorkerFeelibInfo::_workerAddressOffset + 256; +const int UlnWorkerFeelibInfo::_dstEidOffset = UlnWorkerFeelibInfo::_friendWorkerAddressOffset + 256; +const int UlnWorkerFeelibInfo::_rentBalanceOffset = UlnWorkerFeelibInfo::_dstEidOffset + 32; + +const int UlnWorkerFeelibInfo::_rootSliceBits = UlnWorkerFeelibInfo::_rentBalanceOffset + 128; + +int UlnWorkerFeelibInfo::getWorkerAddress(cell $self) impure inline { + return $self.cellPreloadAddressAt(UlnWorkerFeelibInfo::_workerAddressOffset); +} + +cell UlnWorkerFeelibInfo::getWorkerFeeLibStorage(cell $self) impure inline { + return $self.cellPreloadRefAt(1); +} + +;; ==================================== Object Multi-Getters ============================= + +;; (friendAddress, feelibStorage, feelibByteCode) +(int, cell, cell) UlnWorkerFeelibInfo::getQuoteWorkerInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadAddressAt(UlnWorkerFeelibInfo::_friendWorkerAddressOffset), + selfSlice.preloadRefAt(1), + selfSlice.preloadRefAt(0) + ); +} + +;; ==================================== Object Setters ============================= + +cell UlnWorkerFeelibInfo::setWorkerFeeLibBytecode(cell $self, cell $workerFeeLibBytecode) impure inline { + slice selfSlice = $self.begin_parse(); + + return begin_cell() + .store_ref($workerFeeLibBytecode) ;; replace ref[0] with the new workerFeeLibBytecode + .store_slice(selfSlice.sskipfirst(0, 1)) ;; store the rest of the fields + .end_cell(); +} + +cell UlnWorkerFeelibInfo::setWorkerFeeLibStorage(cell $self, cell $workerFeeLibStorage) impure inline { + slice selfSlice = $self.begin_parse(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(UlnWorkerFeelibInfo::_rootSliceBits, 1)) ;; store every bit in root and ref[0] + .store_ref($workerFeeLibStorage) ;; replace ref[1] with the new workerFeeLibStorage + .store_slice(selfSlice.scutlast(0, 1)) ;; store ref[2] + .end_cell(); +} + +cell UlnWorkerFeelibInfo::sanitize(cell $ulnWorkerFeelibInfo) impure { + return UlnWorkerFeelibInfo::New( + $ulnWorkerFeelibInfo.cl::get
(UlnWorkerFeelibInfo::workerAddress), + $ulnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::workerFeelibBytecode), + $ulnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::workerFeelibStorage), + $ulnWorkerFeelibInfo.cl::get
(UlnWorkerFeelibInfo::friendWorkerAddress), + $ulnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::dstEid), + $ulnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::rentBalance), + $ulnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::isAdmin) + ).cl::set( + UlnWorkerFeelibInfo::lastRentTimestamp, + $ulnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::lastRentTimestamp) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/VerificationStatus.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/VerificationStatus.fc new file mode 100644 index 00000000..5b9f5aaa --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/VerificationStatus.fc @@ -0,0 +1,33 @@ +#include "../../../../funC++/classlib.fc"; + +;; required storage name +const int md::VerificationStatus::NAME = "veristatus"u; + +;; field names +const int md::VerificationStatus::nonce = 0; ;; hash +const int md::VerificationStatus::status = 1; ;; uint32 + +cell md::VerificationStatus::New(int nonce, int status) impure inline method_id { + return cl::declare( + md::VerificationStatus::NAME, + unsafeTuple([ + [cl::t::uint64, nonce], ;; md::VerificationStatus::nonce + [cl::t::uint32, status] ;; md::VerificationStatus::status + ]) + ); +} + +;; ==================================== Object Builders ============================= + +const int md::VerificationStatus::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 2); +const int md::VerificationStatus::_headerFillerBits = _HEADER_WIDTH - md::VerificationStatus::_headerInfoBits; +const int md::VerificationStatus::_headerInfo = 38421788582694199859296615363593851; + +cell md::VerificationStatus::build(int nonce, int status) impure inline method_id { + return begin_cell() + .store_uint(md::VerificationStatus::_headerInfo, md::VerificationStatus::_headerInfoBits) ;; header info + .store_ones(md::VerificationStatus::_headerFillerBits) ;; header filler + .store_uint64(nonce) ;; nonce + .store_uint32(status) ;; status + .end_cell(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/tests/serde.fc new file mode 100644 index 00000000..787d38b9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/msgdata/tests/serde.fc @@ -0,0 +1,494 @@ +#include "../../../../../../tests/baseSerdeTest.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../funC++/classlib.fc"; + +#include "../UlnSend.fc"; +#include "../DvnFeesPaidEvent.fc"; +#include "../ExecutorFeePaidEvent.fc"; +#include "../InitUlnConnection.fc"; +#include "../InitUln.fc"; +#include "../SetAdminWorkerAddresses.fc"; +#include "../UlnEvents.fc"; +#include "../UlnReceiveConfig.fc"; +#include "../UlnVerification.fc"; +#include "../UlnWorkerFeelibInfo.fc"; +#include "../VerificationStatus.fc"; +#include "../../../../../funC++/stdlib.fc"; +#include "../../../../../funC++/testutils.fc"; +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Uln Msgdata Serde"; } + +;;; ===============================TESTS========================================= + +;; - Attestation: Has 1 multi-getter +(int, slice) Serde::lz::Attestation::deserialize(cell $unused) impure { + cell $attestation = MOCK_ATTESTATION(12); + + (int hash, int confirmations) = lz::Attestation::deserialize($attestation); + + return test::multiget::equal( + $attestation, + unsafeTuple([lz::Attestation::hash, lz::Attestation::confirmations]), + unsafeTuple([hash, confirmations]) + ); +} + +;; - DvnFeesPaidEvent: Has 1 builder +(int, slice) Serde::DvnFeesPaidEvent::build(cell $unused) impure { + cell requiredDVNs = _buildSendDvnListCell(1, CUSTOM_REQUIRED_DVN0_ADDRESS, false); + cell optionalDVNs = _buildSendDvnListCell(1, CUSTOM_OPTIONAL_DVN0_ADDRESS, false); + cell serializedPayees = serializePayees(unsafeTuple([[WORKER_ADDRESS, NATIVE_FEE]])); + + return test::build::equal( + DvnFeesPaidEvent::New(requiredDVNs, optionalDVNs, serializedPayees), + DvnFeesPaidEvent::build(requiredDVNs, optionalDVNs, serializedPayees) + ); +} + +;; - ExecutorFeePaidEvent: Has 1 builder +(int, slice) Serde::ExecutorFeePaidEvent::build(cell $unused) impure { + return test::build::equal( + ExecutorFeePaidEvent::New(EXECUTOR, MOCK_FEE), + ExecutorFeePaidEvent::build(EXECUTOR, MOCK_FEE) + ); +} + +;; - InitUln: Has 1 sanitize +(int, slice) Serde::InitUln::sanitize(cell $unused) impure { + cell $initUln = md::InitUln::New(MOCK_ULN_CONNECTION_CODE(), TREASURY_FEE_BPS); + + cell $sanitizedInitUln = md::InitUln::sanitize(_dupWithGarbage($initUln)); + + return test::build::equal($initUln, $sanitizedInitUln); +} + +;; - InitUlnConnection: Has 1 sanitize +(int, slice) Serde::InitUlnConnection::sanitize(cell $unused) impure { + cell $initUlnConnection = md::InitUlnConnection::New( + MOCK_CUSTOM_ULN_SEND_CONFIG(), + MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 1, 1), + ENDPOINT_ADDRESS, + CHANNEL_ADDRESS + ); + + cell $sanitizedInitUlnConnection = md::InitUlnConnection::sanitize( + _dupWithGarbage($initUlnConnection) + ); + + return test::build::equal( + $initUlnConnection, + $sanitizedInitUlnConnection + ); +} + +;; - SetAdminWorkerAddresses: Has 1 sanitize +(int, slice) Serde::SetAdminWorkerAddresses::sanitize(cell $unused) impure { + cell $setAdminWorkerAddresses = md::SetAdminWorkerAddresses::New( + MOCK_ADMIN_WORKER_LIST() + ); + + cell $sanitizedSetAdminWorkerAddresses = md::SetAdminWorkerAddresses::sanitize( + _dupWithGarbage($setAdminWorkerAddresses) + ); + + return test::build::equal( + $setAdminWorkerAddresses, + $sanitizedSetAdminWorkerAddresses + ); +} + +;; - UlnEvents: Has 1 builder +(int, slice) Serde::md::UlnEvents::build(cell $unused) impure { + tuple dvnPayees = unsafeTuple([ + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE] + ]); + + cell dvnPaidEvent = DvnFeesPaidEvent::New( + MOCK_CUSTOM_REQUIRED_DVN_CELL(2, 0), + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + serializePayees(dvnPayees) + ); + + cell executorPaidEvent = ExecutorFeePaidEvent::New(CUSTOM_EXECUTOR, CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE); + + return test::build::equal( + UlnEvents::New(cl::nullObject(), dvnPaidEvent, executorPaidEvent), + UlnEvents::build(cl::nullObject(), dvnPaidEvent, executorPaidEvent) + ); +} + +;; - UlnReceiveConfig: Has 4 getters +;; Has 4 multi-getters +;; Has 1 sanitize +(int, slice) Serde::UlnReceiveConfig::getMinCommitPacketGas(cell $unused) impure { + return test::getData::equal( + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + UlnReceiveConfig::getMinCommitPacketGas, + UlnReceiveConfig::minCommitPacketGas + ); +} + +(int, slice) Serde::UlnReceiveConfig::getConfirmationsNull(cell $unused) impure { + return test::getBool::equal( + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + UlnReceiveConfig::getConfirmationsNull, + UlnReceiveConfig::confirmationsNull + ); +} + +(int, slice) Serde::UlnReceiveConfig::getConfirmations(cell $unused) impure { + return test::getData::equal( + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + UlnReceiveConfig::getConfirmations, + UlnReceiveConfig::confirmations + ); +} + +(int, slice) Serde::UlnReceiveConfig::getOptionalDVNThreshold(cell $unused) impure { + return test::getData::equal( + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + UlnReceiveConfig::getOptionalDVNThreshold, + UlnReceiveConfig::optionalDVNThreshold + ); +} + +(int, slice) Serde::UlnReceiveConfig::getRequiredDVNsAndNull(cell $unused) impure { + cell $config = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + ( + int optimizedNull, + cell optimizedDVNs + ) = UlnReceiveConfig::getRequiredDVNsAndNull($config); + + return test::multiget::equal( + $config, + unsafeTuple([UlnReceiveConfig::requiredDVNsNull, UlnReceiveConfig::requiredDVNs]), + unsafeTuple([optimizedNull, optimizedDVNs]) + ); +} + +(int, slice) Serde::UlnReceiveConfig::getOptionalDVNsAndNull(cell $unused) impure { + cell $config = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + ( + int optimizedNull, + cell optimizedDVNs + ) = UlnReceiveConfig::getOptionalDVNsAndNull($config); + + return test::multiget::equal( + $config, + unsafeTuple([UlnReceiveConfig::optionalDVNsNull, UlnReceiveConfig::optionalDVNs]), + unsafeTuple([optimizedNull, optimizedDVNs]) + ); +} + +(int, slice) Serde::UlnReceiveConfig::getRequiredAndOptionalDVNsAndNull(cell $unused) impure { + cell $config = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + ( + int optimizedRequiredNull, + cell optimizedRequiredDVNs, + int optimizedOptionalNull, + cell optimizedOptionalDVNs + ) = UlnReceiveConfig::getRequiredAndOptionalDVNsAndNull($config); + + return test::multiget::equal( + $config, + unsafeTuple([UlnReceiveConfig::requiredDVNsNull, UlnReceiveConfig::requiredDVNs, UlnReceiveConfig::optionalDVNsNull, UlnReceiveConfig::optionalDVNs]), + unsafeTuple([optimizedRequiredNull, optimizedRequiredDVNs, optimizedOptionalNull, optimizedOptionalDVNs]) + ); +} + +(int, slice) Serde::UlnReceiveConfig::getCommitInfo(cell $unused) impure { + cell $config = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + ( + int optimizedMinCommitPacketGasNull, + int optimizedMinCommitPacketGas, + int optimizedOptionalDVNsNull, + int optimizedOptionalDVNThreshold + ) = UlnReceiveConfig::getCommitInfo($config); + + return test::multiget::equal( + $config, + unsafeTuple([ + UlnReceiveConfig::minCommitPacketGasNull, + UlnReceiveConfig::minCommitPacketGas, + UlnReceiveConfig::optionalDVNsNull, + UlnReceiveConfig::optionalDVNThreshold + ]), + unsafeTuple([ + optimizedMinCommitPacketGasNull, + optimizedMinCommitPacketGas, + optimizedOptionalDVNsNull, + optimizedOptionalDVNThreshold + ]) + ); +} + +(int, slice) Sedre::UlnReceiveConfig::sanitize(cell $unused) impure { + cell $ulnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + cell $sanitizedUlnReceiveConfig = UlnReceiveConfig::sanitize( + _dupWithGarbage($ulnReceiveConfig) + ); + + return test::build::equal( + $ulnReceiveConfig, + $sanitizedUlnReceiveConfig + ); +} + +;; - Uln Send: Has 1 builder +;; Has 2 getters +;; Has 1 multi-getter +(int, slice) Serde::md::UlnSend::build(cell $unused) impure { + cell $lzSend = MOCK_LZ_SEND(); + cell $customUlnSendConfig = MOCK_CUSTOM_ULN_SEND_CONFIG(); + + cell $connectionInitialStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ); + + int forwardingAddress = CHANNEL_ADDRESS; + + return test::build::equal( + md::UlnSend::New($lzSend, $customUlnSendConfig, $connectionInitialStorage, forwardingAddress), + md::UlnSend::build($lzSend, $customUlnSendConfig, $connectionInitialStorage, forwardingAddress) + ); +} + +(int, slice) Serde::md::UlnSend::getConnectionInitialStorage(cell $unused) impure { + return test::getRef::equal( + MOCK_ULN_SEND(), + md::UlnSend::getConnectionInitialStorage, + md::UlnSend::connectionInitialStorage + ); +} + +(int, slice) Serde::md::UlnSend::getForwardingAddress(cell $unused) impure { + return test::getData::equal( + MOCK_ULN_SEND(), + md::UlnSend::getForwardingAddress, + md::UlnSend::forwardingAddress + ); +} + +(int, slice) Serde::md::UlnSend::getLzSendAndCustomUlnSendConfig(cell $unused) impure { + cell $ulnSend = MOCK_ULN_SEND(); + + cell $expectedLzSend = $ulnSend.cl::get(md::UlnSend::lzSend); + cell $expectedCustomUlnSendConfig = $ulnSend.cl::get(md::UlnSend::customUlnSendConfig); + + ( + cell $lzSend, + cell $customUlnSendConfig + ) = md::UlnSend::getLzSendAndCustomUlnSendConfig($ulnSend); + + return test::multiget::equal( + $ulnSend, + unsafeTuple([md::UlnSend::lzSend, md::UlnSend::customUlnSendConfig]), + unsafeTuple([$lzSend, $customUlnSendConfig]) + ); +} + +;; - Uln Send Config: Has 1 sanitize +(int, slice) Serde::UlnSendConfig::sanitize(cell $unused) impure { + cell $ulnSendConfig = MOCK_CUSTOM_ULN_SEND_CONFIG(); + + cell $sanitizedUlnSendConfig = UlnSendConfig::sanitize( + _dupWithGarbage($ulnSendConfig) + ); + + return test::build::equal( + $ulnSendConfig, + $sanitizedUlnSendConfig + ); +} + +;; - Uln Verification: Has 1 multi-getter +(int, slice) Serde::md::UlnVerification::deserialize(cell $unused) impure { + cell $ulnVerification = md::UlnVerification::New(NONCE, PACKET_HASH, DEFAULT_CONFIRMATIONS); + + ( + int nonce, + cell $attestation + ) = md::UlnVerification::deserialize($ulnVerification); + + return test::multiget::equal( + $ulnVerification, + unsafeTuple([md::UlnVerification::nonce, md::UlnVerification::attestation]), + unsafeTuple([nonce, $attestation]) + ); +} + +;; - Uln Worker Feelib Bytecode: Has 1 sanitize +(int, slice) Serde::UlnWorkerFeelibBytecode::sanitize(cell $unused) impure { + cell $ulnWorkerFeelibBytecode = MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD(); + + cell $sanitizedUlnWorkerFeelibBytecode = md::UlnWorkerFeelibBytecode::sanitize( + _dupWithGarbage($ulnWorkerFeelibBytecode) + ); + + return test::build::equal( + $ulnWorkerFeelibBytecode, + $sanitizedUlnWorkerFeelibBytecode + ); +} + +;; - UlnWorkerFeelibInfo: Has 2 getters +;; Has 1 multi-getter +;; Has 2 setters +(int, slice) Serde::UlnWorkerFeelibInfo::getWorkerAddress(cell $unused) impure { + return test::getData::equal( + MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1), Uln::CONST::INITIAL_RENT_NANOS), + UlnWorkerFeelibInfo::getWorkerAddress, + UlnWorkerFeelibInfo::workerAddress + ); +} + +(int, slice) Serde::UlnWorkerFeelibInfo::getWorkerFeeLibStorage(cell $unused) impure { + return test::getRef::equal( + MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1), Uln::CONST::INITIAL_RENT_NANOS), + UlnWorkerFeelibInfo::getWorkerFeeLibStorage, + UlnWorkerFeelibInfo::workerFeelibStorage + ); +} + +(int, slice) Serde::UlnWorkerFeelibInfo::getQuoteWorkerInformation(cell $unused) impure { + cell $ulnWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1), Uln::CONST::INITIAL_RENT_NANOS + ); + + ( + int friendWorkerAddress, + cell $workerFeelibStorage, + cell $workerFeelibBytecode + ) = UlnWorkerFeelibInfo::getQuoteWorkerInformation($ulnWorkerFeelibInfo); + + return test::multiget::equal( + $ulnWorkerFeelibInfo, + unsafeTuple([UlnWorkerFeelibInfo::friendWorkerAddress, UlnWorkerFeelibInfo::workerFeelibStorage, UlnWorkerFeelibInfo::workerFeelibBytecode]), + unsafeTuple([friendWorkerAddress, $workerFeelibStorage, $workerFeelibBytecode]) + ); +} + +(int, slice) Serde::UlnWorkerFeelibInfo::setWorkerFeeLibBytecode(cell $unused) impure { + cell $workerFeeLibInfo = MOCK_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + Uln::CONST::INITIAL_RENT_NANOS + ); + + cell $expectedWorkerFeeLibInfo = $workerFeeLibInfo.cl::set( + UlnWorkerFeelibInfo::workerFeelibBytecode, + _getRandomCode(55) + ); + cell $optimizedWorkerFeeLibInfo = UlnWorkerFeelibInfo::setWorkerFeeLibBytecode( + $workerFeeLibInfo, + _getRandomCode(55) + ); + + return test::set::equal( + $expectedWorkerFeeLibInfo, + $optimizedWorkerFeeLibInfo + ); +} + +(int, slice) Serde::UlnWorkerFeelibInfo::setWorkerFeeLibStorage(cell $unused) impure { + cell $workerFeeLibInfo = MOCK_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + Uln::CONST::INITIAL_RENT_NANOS + ); + + cell $expectedWorkerFeeLibInfo = $workerFeeLibInfo.cl::set( + UlnWorkerFeelibInfo::workerFeelibStorage, + _getRandomCode(65) + ); + cell $optimizedWorkerFeeLibInfo = UlnWorkerFeelibInfo::setWorkerFeeLibStorage( + $workerFeeLibInfo, + _getRandomCode(65) + ); + + return test::set::equal( + $expectedWorkerFeeLibInfo, + $optimizedWorkerFeeLibInfo + ); +} + +(int, slice) Serde::UlnWorkerFeelibInfo::sanitize(cell $unused) impure { + cell $ulnWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1), Uln::CONST::INITIAL_RENT_NANOS + ); + + cell $sanitizedUlnWorkerFeelibInfo = UlnWorkerFeelibInfo::sanitize( + _dupWithGarbage($ulnWorkerFeelibInfo) + ); + + return test::build::equal( + $ulnWorkerFeelibInfo, + $sanitizedUlnWorkerFeelibInfo + ); +} + +;; - Verification Status: Has 1 builder +(int, slice) Serde::md::VerificationStatus::build(cell $unused) impure { + return test::build::equal( + md::VerificationStatus::New(NONCE, UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED), + md::VerificationStatus::build(NONCE, UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + ;; - Attestation + .tpush([Serde::lz::Attestation::deserialize, "Serde::lz::Attestation::deserialize"]) + ;; - Dvn Fees Paid Event + .tpush([Serde::DvnFeesPaidEvent::build, "Serde::DvnFeesPaidEvent::build"]) + ;; - Executor Fee Paid Event + .tpush([Serde::ExecutorFeePaidEvent::build, "Serde::ExecutorFeePaidEvent::build"]) + ;; - InitUlnConnection + .tpush([Serde::InitUlnConnection::sanitize, "Serde::InitUlnConnection::sanitize"]) + ;; - InitUln + .tpush([Serde::InitUln::sanitize, "Serde::InitUln::sanitize"]) + ;; - SetAdminWorkerAddresses + .tpush([Serde::SetAdminWorkerAddresses::sanitize, "Serde::SetAdminWorkerAddresses::sanitize"]) + ;; - Uln Events + .tpush([Serde::md::UlnEvents::build, "Serde::md::UlnEvents::build"]) + ;; - Uln Receive Config + .tpush([Serde::UlnReceiveConfig::getMinCommitPacketGas, "Serde::UlnReceiveConfig::getMinCommitPacketGas"]) + .tpush([Serde::UlnReceiveConfig::getConfirmationsNull, "Serde::UlnReceiveConfig::getConfirmationsNull"]) + .tpush([Serde::UlnReceiveConfig::getConfirmations, "Serde::UlnReceiveConfig::getConfirmations"]) + .tpush([Serde::UlnReceiveConfig::getOptionalDVNThreshold, "Serde::UlnReceiveConfig::getOptionalDVNThreshold"]) + .tpush([Serde::UlnReceiveConfig::getRequiredDVNsAndNull, "Serde::UlnReceiveConfig::getRequiredDVNsAndNull"]) + .tpush([Serde::UlnReceiveConfig::getOptionalDVNsAndNull, "Serde::UlnReceiveConfig::getOptionalDVNsAndNull"]) + .tpush([Serde::UlnReceiveConfig::getRequiredAndOptionalDVNsAndNull, "Serde::UlnReceiveConfig::getRequiredAndOptionalDVNsAndNull"]) + .tpush([Serde::UlnReceiveConfig::getCommitInfo, "Serde::UlnReceiveConfig::getCommitInfo"]) + .tpush([Sedre::UlnReceiveConfig::sanitize, "Serde::UlnReceiveConfig::sanitize"]) + ;; - Uln Send + .tpush([Serde::md::UlnSend::build, "Serde::md::UlnSend::build"]) + .tpush([Serde::md::UlnSend::getConnectionInitialStorage, "Serde::md::UlnSend::getConnectionInitialStorage"]) + .tpush([Serde::md::UlnSend::getForwardingAddress, "Serde::md::UlnSend::getForwardingAddress"]) + .tpush([Serde::md::UlnSend::getLzSendAndCustomUlnSendConfig, "Serde::md::UlnSend::getLzSendAndCustomUlnSendConfig"]) + ;; - Uln Send Config + .tpush([Serde::UlnSendConfig::sanitize, "Serde::UlnSendConfig::sanitize"]) + ;; - Uln Verification + .tpush([Serde::md::UlnVerification::deserialize, "Serde::md::UlnVerification::deserialize"]) + ;; - Uln Worker Feelib Bytecode + .tpush([Serde::UlnWorkerFeelibBytecode::sanitize, "Serde::UlnWorkerFeelibBytecode::sanitize"]) + ;; - Uln Worker Feelib Info + .tpush([Serde::UlnWorkerFeelibInfo::getWorkerAddress, "Serde::UlnWorkerFeelibInfo::getWorkerAddress"]) + .tpush([Serde::UlnWorkerFeelibInfo::getWorkerFeeLibStorage, "Serde::UlnWorkerFeelibInfo::getWorkerFeeLibStorage"]) + .tpush([Serde::UlnWorkerFeelibInfo::getQuoteWorkerInformation, "Serde::UlnWorkerFeelibInfo::getQuoteWorkerInformation"]) + .tpush([Serde::UlnWorkerFeelibInfo::setWorkerFeeLibBytecode, "Serde::UlnWorkerFeelibInfo::setWorkerFeeLibBytecode"]) + .tpush([Serde::UlnWorkerFeelibInfo::setWorkerFeeLibStorage, "Serde::UlnWorkerFeelibInfo::setWorkerFeeLibStorage"]) + .tpush([Serde::UlnWorkerFeelibInfo::sanitize, "Serde::UlnWorkerFeelibInfo::sanitize"]) + ;; - Verification Status + .tpush([Serde::md::VerificationStatus::build, "Serde::md::VerificationStatus::build"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/handler.fc new file mode 100644 index 00000000..a9e24a01 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/handler.fc @@ -0,0 +1,787 @@ +#include "../../BytesEncoder.fc"; + + +#include "../feeLibInterface.fc"; +#include "../feeLibUtils.fc"; + +#include "../msgdata/DvnFeesPaidEvent.fc"; +#include "../msgdata/ExecutorFeePaidEvent.fc"; +#include "../msgdata/InitUln.fc"; +#include "../msgdata/RentRefill.fc"; +#include "../msgdata/TreasuryFeeBps.fc"; +#include "../msgdata/UlnEvents.fc"; +#include "../msgdata/UlnSend.fc"; +#include "../msgdata/UlnSendConfig.fc"; +#include "../msgdata/UlnWorkerFeelibEvents.fc"; +#include "../msgdata/UlnWorkerFeelibInfo.fc"; + +#include "../ulnConnection/interface.fc"; +#include "../ulnManager/interface.fc"; + +#include "../../../interfaces.fc"; +#include "../../../channel/interface.fc"; +#include "../../../core/abstract/protocolHandler.fc"; + +#include "../../../../classes/lz/Packet.fc"; +#include "../../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../../classes/msgdata/LzSend.fc"; +#include "../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../classes/msgdata/MdObj.fc"; +#include "../../../../classes/msgdata/MsglibSendCallback.fc"; +#include "../../../../classes/msgdata/SetAddress.fc"; + +#include "interface.fc"; +#include "storage.fc"; + +const int _quoteWorkersMethodId = 23432; +;;; ================INTERFACE FUNCTIONS===================== + +(cell, tuple) _initialize(cell $initUln) impure inline { + (cell $storage, tuple actions) = preamble(); + + $storage = $storage + .cl::set( + Uln::connectionCode, + $initUln.cl::get(md::InitUln::connectionCode) + ) + .cl::set( + Uln::treasuryFeeBps, + $initUln.cl::get(md::InitUln::treasuryFeeBps) + ) + .cl::set( + Uln::remainingWorkerSlots, + Uln::MaxWorkerFeelibs - UlnManager::CONST::MAX_ADMIN_WORKERS + ) + .cl::set( + Uln::remainingAdminWorkerSlots, + UlnManager::CONST::MAX_ADMIN_WORKERS + ); + + return ($storage, actions); +} + +int _getEventSink() impure { + return getOwner(); +} + +() assertConnection(cell $ulnSendMd) impure inline { + cell connectionCode = getContractStorage().Uln::getConnectionCode(); + + cell $connectionInitialStorage = $ulnSendMd.md::UlnSend::getConnectionInitialStorage(); + + int providedUlnManagerAddress = + UlnConnection::getBaseStorage($connectionInitialStorage).BaseStorage::getOwner(); + + ;; By construction, if the bytecode is valid and the uln manager address is correct, + ;; then the uln address cannot be incorrect (assuming no bug in the ULN connection bytecode). + throw_unless( + Uln::ERROR::notUlnConnection, + (providedUlnManagerAddress == getOwner()) + & (computeContractAddress($connectionInitialStorage, connectionCode) == getCaller()) + ); +} + +cell _failedMsglibSendCallback(cell $lzSend) impure inline { + return md::MsglibSendCallback::New( + 0, + 0, + $lzSend, + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ); +} + +cell _quote(cell $ulnSendMd, cell $storage) impure inline { + ( + cell $lzSend, + cell $customUlnSendConfig + ) = $ulnSendMd.md::UlnSend::getLzSendAndCustomUlnSendConfig(); + + cell $defaultUlnSendConfig = $storage.Uln::getDefaultUlnSendConfig(); + + ( + int quoteGasLimit, + int maxMsgBytes, + int executor, + cell requiredDVNs, + cell optionalDVNs, + int confirmations, + int isValid + ) = lz::UlnSendConfig::resolveUlnSendConfig( + $customUlnSendConfig, + $defaultUlnSendConfig + ); + + ifnot (isValid) { + return _failedMsglibSendCallback($lzSend); + } + + ( + cell $packet, + cell $extraOptions, + cell $enforcedOptions + ) = $lzSend.md::LzSend::getQuoteInformation(); + + int messageBytes = lz::Packet::_messageBytes($packet); + + tuple returnStack = unsafeTuple([null()]); + + if (messageBytes <= maxMsgBytes) { + returnStack = safePackedInputsRunVm( + unsafeTuple([ + $storage, + $lzSend, + executor, + requiredDVNs, + optionalDVNs, + confirmations, + messageBytes, + $extraOptions, + $enforcedOptions + ]), + _quoteWorkersMethodId, + 1, + my_code().begin_parse(), + quoteGasLimit + ); + } + + cell $msglibSendCallback = returnStack.cell_at(0); + ifnot ($msglibSendCallback.is_cell()) { + return _failedMsglibSendCallback($lzSend); + } + + return $msglibSendCallback; +} + +;; caller is responsible for asserting the worker exists +cell _removeWorker(cell $storage, cell $workerFeelibInfo) impure inline { + int workerAddress = $workerFeelibInfo.cl::get
(UlnWorkerFeelibInfo::workerAddress); + int workerSlotField = $workerFeelibInfo.cl::get(UlnWorkerFeelibInfo::isAdmin) + ? Uln::remainingAdminWorkerSlots + : Uln::remainingWorkerSlots; + return $storage + .cl::nestedDict256::delete(Uln::workerFeelibInfos, workerAddress) + .cl::set(workerSlotField, $storage.cl::get(workerSlotField) + 1); +} + +(int, cell) _quoteWorker( + cell $storage, + int workerAddress, + cell $path, + int confirmations, + int packetBytes, + cell $extraOptions, + cell $enforcedOptions +) impure inline_ref method_id { + cell $workerFeelibInfos = $storage.Uln::getWorkerFeelibInfos(); + + (cell $workerInfo, int workerExists) = $workerFeelibInfos.cl::dict256::get(workerAddress); + ifnot (workerExists) { + return (-1, null()); + } + + ( int friendWorkerAddress, + cell $workerFeelibStorage, + cell workerFeelibBytecode + ) = $workerInfo.UlnWorkerFeelibInfo::getQuoteWorkerInformation(); + + (cell $friendInfo, int friendExists) = ($workerInfo, true); + + ifnot (friendWorkerAddress == NULLADDRESS) { + ($friendInfo, friendExists) = $workerFeelibInfos.cl::dict256::get(friendWorkerAddress); + } + ifnot (friendExists) { + return (-1, null()); + } + + tuple returnStack = safePackedInputsRunVm( + unsafeTuple( + [ + $workerFeelibStorage, + $friendInfo, + $path, + confirmations, + packetBytes, + $extraOptions, + $enforcedOptions + ] + ), + UlnWorkerInterface::quote, + 1, + workerFeelibBytecode.begin_parse(), + MAX_U32 ;; gas limit doesn't matter for the inner call + ); + + tuple retVal = returnStack.tuple_at(0); + ;; if it's not a tuple, then the worker returned an error or went OOG + ifnot (retVal.is_tuple()) { + return (-1, null()); + } + + ;; the tuple must have exactly two items + ifnot (retVal.tlen() == 2) { + return (-1, null()); + } + + ;; the first item must be an int + int fee = retVal.int_at(0); + ifnot (fee.is_int()) { + return (-1, null()); + } + + ;; the second item must be a cell or null + cell event = retVal.cell_at(1); + ifnot ((event.is_cell()) | (event.is_null())) { + return (-1, null()); + } + + return (fee, event); +} + +;; return null on failure, actions on success +;; tuple _quoteWorkers(cell $lzSend, int executor, cell requiredDVNs, cell optionalDVNs) impure inline { +cell _quoteWorkers(tuple args) impure inline method_id(23432) { + cell $storage = args.cell_at(0); + cell $lzSend = args.cell_at(1); + int executor = args.int_at(2); + cell requiredDVNsCell = args.cell_at(3); + cell optionalDVNsCell = args.cell_at(4); + int confirmations = args.int_at(5); + int packetBytes = args.int_at(6); + cell $extraOptions = args.cell_at(7); + cell $enforcedOptions = args.cell_at(8); + ;; quote the executor first + int totalNativeFee = 0; + + slice requiredDVNs = requiredDVNsCell.begin_parse(); + slice optionalDVNs = optionalDVNsCell.begin_parse(); + + cell $packet = $lzSend.md::LzSend::getPacket(); + cell $path = $packet.lz::Packet::getPath(); + + tuple payeesInfo = empty_tuple(); + tuple workerEvents = UlnWorkerFeelibEventsBuilder::create(); + + while (requiredDVNs.slice_empty?() == false) { + int requiredDVNAddress = requiredDVNs~AddressList::next(); + (int nativeFee, cell eventBody) = _quoteWorker( + $storage, + requiredDVNAddress, + $path, + confirmations, + packetBytes, + $extraOptions, + $enforcedOptions + ); + + if (nativeFee < 0) { + return null(); + } + + totalNativeFee += nativeFee; + payeesInfo = payeesInfo.tpush(unsafeTuple([requiredDVNAddress, nativeFee])); + workerEvents = workerEvents + .UlnWorkerFeelibEventsBuilder::push(requiredDVNAddress, eventBody); + } + + while (optionalDVNs.slice_empty?() == false) { + int optionalDVNAddress = optionalDVNs~AddressList::next(); + (int nativeFee, cell eventBody) = _quoteWorker( + $storage, + optionalDVNAddress, + $path, + confirmations, + packetBytes, + $extraOptions, + $enforcedOptions + ); + + if (nativeFee < 0) { + return null(); + } + + payeesInfo = payeesInfo.tpush(unsafeTuple([optionalDVNAddress, nativeFee])); + totalNativeFee += nativeFee; + + workerEvents = workerEvents + .UlnWorkerFeelibEventsBuilder::push(optionalDVNAddress, eventBody); + } + + ;; create the dvnPaidEvent + cell $dvnsFeesPaidEvent = DvnFeesPaidEvent::build( + requiredDVNsCell, + optionalDVNsCell, + serializePayees(payeesInfo) + ); + + (int executorFee, cell eventBody) = _quoteWorker( + $storage, + executor, + $path, + confirmations, + packetBytes, + $extraOptions, + $enforcedOptions + ); + + if (executorFee < 0) { + return null(); + } + + ;; add the executor fee + payeesInfo = payeesInfo.tpush(unsafeTuple([executor, executorFee])); + totalNativeFee += executorFee; + + ;; add events from executor to workerEvents + workerEvents = workerEvents + .UlnWorkerFeelibEventsBuilder::push(executor, eventBody); + + ;; create the executorPaidEvent + cell $executorFeePaidEvent = ExecutorFeePaidEvent::build(executor, executorFee); + + ;; Handle treasury fee + (int owner, int treasuryFeeBps) = $storage.Uln::getQuoteWorkersInformation(); + int treasuryFee = totalNativeFee * treasuryFeeBps / 10000; + payeesInfo = payeesInfo.tpush( + unsafeTuple([owner, treasuryFee]) + ); + + return md::MsglibSendCallback::build( + totalNativeFee + treasuryFee, + 0, + $lzSend, + BytesEncoder::build($packet).BytesEncoder::serialize(), + serializePayees(payeesInfo), + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + UlnEvents::build( + UlnWorkerFeelibEvents::FromBuilder(workerEvents), + $dvnsFeesPaidEvent, + $executorFeePaidEvent + ), + Channel::NO_ERROR + ); +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + if (op == Uln::OP::ULN_SEND) { + return assertConnection($md); + } elseif ( + (op == Uln::OP::ULN_VERIFY) + | (op == Uln::OP::ULN_COMMIT_PACKET) + | (op == Uln::OP::ULN_QUOTE) + ) { + return (); + } elseif ( + (op == Uln::OP::DEREGISTER_WORKER_FEELIB) + | (op == Uln::OP::COLLECT_WORKER_RENT) + | (op == Uln::OP::SET_WORKER_FEELIB_STORAGE) + | (op == Uln::OP::REFILL_WORKER_RENT) + | (op == Uln::OP::GC_ATTESTATIONS) + ) { + return (); + } elseif ( + (op == Uln::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG) + | (op == Uln::OP::SET_DEFAULT_ULN_SEND_CONFIG) + | (op == Uln::OP::UPDATE_WORKER_FEELIB) + | (op == Uln::OP::SET_TREASURY_FEE_BPS) + ) { + return assertOwner(); + } else { + ;; we must put a check for all opcodes to make sure we don't + ;; mistakenly miss an opp code's permissions + throw(BaseInterface::ERROR::invalidOpcode); + } +} + +;;; ==========================HANDLERS===================================== + +tuple ulnQuote(cell $ulnSendMd) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + actions~pushAction( + $ulnSendMd.md::UlnSend::getForwardingAddress(), + Msglib::OP::RETURN_QUOTE, + _quote($ulnSendMd, $storage) + ); + + return actions; +} + +tuple ulnSend(cell $ulnSendMd) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + actions~pushAction( + $ulnSendMd.md::UlnSend::getForwardingAddress(), + Channel::OP::MSGLIB_SEND_CALLBACK, + _quote($ulnSendMd, $storage) + ); + + return actions; +} + +;; @in_opcode Msglibs::OP::ULN_COMMIT_VERIFICATION +;; @in frame +;; @in $mdAddress { address: UlnConnectionAddress, md: Packet } +;; @permissions permissionless +;; @out_actions call commit on connection +tuple ulnCommitPacket(cell $mdAddress) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + (cell $packet, int ulnConnectionAddress) = $mdAddress.md::MdAddress::deserialize(); + + actions~pushAction( + ulnConnectionAddress, + UlnConnection::OP::ULN_CONNECTION_COMMIT_PACKET, + md::MdObj::build( + $packet, + $storage.Uln::getDefaultUlnReceiveConfig() + ) + ); + + return actions; +} + +;; @in_opcode Msglibs::OP::VERIFY +;; @in external caller +;; @in $mdAddress { address: UlnConnectionAddress, md: Verification } +;; @permissions permissionless +;; @out_actions call verify on connection +;; @notice this is an optional function that will +;; call ulnConnectionVerify on the connection using a default config +tuple ulnVerify(cell $mdAddress) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + (cell $md, int ulnConnectionAddress) = md::MdAddress::deserialize($mdAddress); + + actions~pushAction( + ulnConnectionAddress, + UlnConnection::OP::ULN_CONNECTION_VERIFY, + md::ExtendedMd::build( + $md, + $storage.Uln::getDefaultUlnReceiveConfig(), + getCaller() + ) + ); + + return actions; +} + +() _setWorkerInfo(cell $workerFeelibInfos, cell $workerInfo) impure inline_ref { + cell $storage = getContractStorage(); + int workerAddress = $workerInfo.UlnWorkerFeelibInfo::getWorkerAddress(); + + ;; Restrict the size of the new storage to prevent DoS of our contract storage. + ;; The total worker bytecode size is capped by UlnManager::CONST::MAX_CUMULATIVE_BYTECODE_CELLS + ;; and enforced by the UlnManager, so here the only thing we need to check is the storage size + ;; of the workers, excluding their bytecode + (_, _, _, int success) = compute_data_size?( + $workerInfo.UlnWorkerFeelibInfo::setWorkerFeeLibBytecode(empty_cell()), + Uln::WorkerFeelibInfo::MaxCells + ); + + throw_unless(Uln::ERROR::invalidWorkerStorage, success); + + setContractStorage( + $storage.Uln::updateWorkerFeelibInfos( + workerAddress, + $workerInfo + ) + ); +} + +tuple updateWorkerFeelib(cell $UlnWorkerFeelibInfo) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + int workerAddress = $UlnWorkerFeelibInfo.cl::get
(UlnWorkerFeelibInfo::workerAddress); + + cell $workerFeelibInfos = $storage.Uln::getWorkerFeelibInfos(); + + (cell $existingWorkerFeelibInfo, int exists) = $workerFeelibInfos.cl::dict256::get(workerAddress); + + ifnot (exists) { + int isAdmin = $UlnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::isAdmin); + int workerSlotField = isAdmin ? Uln::remainingAdminWorkerSlots : Uln::remainingWorkerSlots; + int remainingWorkerSlots = $storage.cl::get(workerSlotField); + + if (remainingWorkerSlots == 0) { + ;; exit early if there are no remaining worker slots + actions~pushAction( + Uln::event::ULN_WORKER_SLOTS_FULL, + $UlnWorkerFeelibInfo + ); + return actions; + } + + ifnot (isAdmin) { + ;; if it doesn't exist, we must collect the initial rent deposit + int initialRent = $UlnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::rentBalance); + + throw_unless(Uln::ERROR::insufficientRent, initialRent >= Uln::CONST::INITIAL_RENT_NANOS); + + actions~pushAction( + getOwner(), + initialRent, + 0 + ); + } + + $UlnWorkerFeelibInfo = $UlnWorkerFeelibInfo.cl::set( + UlnWorkerFeelibInfo::lastRentTimestamp, + now() + ); + + setContractStorage( + $storage.cl::set(workerSlotField, remainingWorkerSlots - 1) + ); + } else { + ;; Do not allow workers to arbitrarily change their rent information + $UlnWorkerFeelibInfo = $UlnWorkerFeelibInfo + .cl::set( + UlnWorkerFeelibInfo::rentBalance, + $existingWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::rentBalance) + ) + .cl::set( + UlnWorkerFeelibInfo::lastRentTimestamp, + $existingWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::lastRentTimestamp) + ); + } + + _setWorkerInfo($workerFeelibInfos, $UlnWorkerFeelibInfo); + + actions~pushAction( + Uln::event::ULN_WORKER_REGISTERED, + $UlnWorkerFeelibInfo + ); + + return actions; +} + +tuple deregisterWorkerFeelib(cell $empty) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + (cell $UlnWorkerFeelibInfo, int exists) = cl::nestedDict256::get( + $storage, + Uln::workerFeelibInfos, + getCaller() ;; = workerAddress + ); + + if (exists) { + setContractStorage( + _removeWorker($storage, $UlnWorkerFeelibInfo) + ); + actions~pushAction( + Uln::event::ULN_WORKER_DEREGISTERED, + $UlnWorkerFeelibInfo + ); + } + + return actions; +} + +;; This function handles ALL worker configs including priceFeed updates +;; @out_actions event +tuple setWorkerFeelibStorage(cell $newStorage) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $workerFeelibInfos = $storage.Uln::getWorkerFeelibInfos(); + + int workerAddress = getCaller(); + + (cell $workerInfo, int exists) = $workerFeelibInfos.cl::dict256::get(workerAddress); + throw_unless(Uln::ERROR::nonexistentWorker, exists); + + cell $updatedWorkerInfo = $workerInfo + .UlnWorkerFeelibInfo::setWorkerFeeLibStorage($newStorage); + + _setWorkerInfo($workerFeelibInfos, $updatedWorkerInfo); + + actions~pushAction( + Uln::event::ULN_WORKER_STORAGE_SET, + $updatedWorkerInfo + ); + + actions~pushAction( + workerAddress, + Uln::OP::SET_WORKER_FEELIB_STORAGE_CALLBACK, + $newStorage + ); + + return actions; +} + +tuple refillWorkerRent(cell $rentRefill) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $workerFeelibInfos = $storage.Uln::getWorkerFeelibInfos(); + + int workerAddress = $rentRefill.cl::get
(md::RentRefill::address); + int amount = $rentRefill.cl::get(md::RentRefill::amount); + (cell $workerInfo, int exists) = $workerFeelibInfos.cl::dict256::get(workerAddress); + + throw_unless(Uln::ERROR::nonexistentWorker, exists); + + int currentRentBalance = $workerInfo.cl::get(UlnWorkerFeelibInfo::rentBalance); + + _setWorkerInfo( + $workerFeelibInfos, + $workerInfo.cl::set(UlnWorkerFeelibInfo::rentBalance, currentRentBalance + amount) + ); + + actions~pushAction( + getOwner(), + amount, + 0 + ); + + actions~pushAction( + Uln::event::ULN_WORKER_RENT_REFILLED, + $rentRefill + ); + + return actions; +} + +tuple collectWorkerRent(cell $setAddress) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $workerFeelibInfos = $storage.Uln::getWorkerFeelibInfos(); + + (cell $workerInfo, int exists) = $workerFeelibInfos.cl::dict256::get( + $setAddress.cl::get
(md::SetAddress::address) + ); + + throw_unless(Uln::ERROR::nonexistentWorker, exists); + + ;; Admins do not pay rent + if ($workerInfo.cl::get(UlnWorkerFeelibInfo::isAdmin)) { + return actions; + } + + int currentTimestamp = now(); + int elapsedTimestamp = currentTimestamp - $workerInfo.cl::get(UlnWorkerFeelibInfo::lastRentTimestamp); + throw_unless(Uln::ERROR::invalidTimestamp, elapsedTimestamp > 0); + + int totalRentOwed = elapsedTimestamp * Uln::CONST::RENT_NANOS_PER_SECOND; + int currentRentBalance = $workerInfo.cl::get(UlnWorkerFeelibInfo::rentBalance); + + ;; if rentBalance is greater than totalRentOwed, we can update rentBalance and lastRentTimestamp + if (currentRentBalance >= totalRentOwed) { + $workerInfo = $workerInfo + .cl::set(UlnWorkerFeelibInfo::rentBalance, currentRentBalance - totalRentOwed) + .cl::set(UlnWorkerFeelibInfo::lastRentTimestamp, currentTimestamp); + + ;; update workerInfo in storage + _setWorkerInfo($workerFeelibInfos, $workerInfo); + + actions~pushAction( + Uln::event::ULN_COLLECT_WORKER_RENT, + $workerInfo + ); + } else { + ;; else we must delete(evict) the worker from the workerInfos + setContractStorage( + _removeWorker($storage, $workerInfo) + ); + + actions~pushAction( + Uln::event::ULN_WORKER_EVICTED, + $workerInfo + ); + } + + return actions; +} + +tuple setDefaultUlnSendConfig(cell $ulnSendConfig) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedUlnSendConfig = UlnSendConfig::sanitize($ulnSendConfig); + + setContractStorage( + $storage.cl::set( + Uln::defaultUlnSendConfig, + $sanitizedUlnSendConfig + ) + ); + + actions~pushAction( + Uln::event::ULN_DEFAULT_SEND_CONFIG_SET, + $sanitizedUlnSendConfig + ); + + return actions; +} + +tuple setDefaultUlnReceiveConfig(cell $ulnReceiveConfig) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedUlnReceiveConfig = UlnReceiveConfig::sanitize($ulnReceiveConfig); + + setContractStorage( + $storage.cl::set( + Uln::defaultUlnReceiveConfig, + $sanitizedUlnReceiveConfig + ) + ); + + actions~pushAction( + Uln::event::ULN_DEFAULT_RECEIVE_CONFIG_SET, + $sanitizedUlnReceiveConfig + ); + + return actions; +} + +tuple setTreasuryFeeBps(cell $treasuryFeeBps) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + int treasuryFeeBps = $treasuryFeeBps.cl::get(md::TreasuryFeeBps::treasuryFeeBps); + + throw_unless(Uln::ERROR::invalidTreasuryFeeBps, treasuryFeeBps <= 10000); + + setContractStorage( + $storage.cl::set( + Uln::treasuryFeeBps, + treasuryFeeBps + ) + ); + + actions~pushAction( + Uln::event::ULN_TREASURY_FEE_BPS_SET, + $treasuryFeeBps + ); + + return actions; +} + +;; Send the default config to the ULN connection to allow it to garbage collect +;; attestations from DVNs that were removed from the configuration +tuple garbageCollectInvalidAttestations(cell $mdAddress) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + actions~pushAction( + $mdAddress.cl::get
(md::MdAddress::address), + UlnConnection::OP::GARBAGE_COLLECT_INVALID_ATTESTATIONS, + md::MdObj::build( + $mdAddress.cl::get(md::MdAddress::md), + $storage.cl::get(Uln::defaultUlnReceiveConfig) + ) + ); + + return actions; +} + +(int, int, int) version() impure method_id { + return (3, 0, 2); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/interface.fc new file mode 100644 index 00000000..03d98552 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/interface.fc @@ -0,0 +1,63 @@ +#include "storage.fc"; + +;; only-owner (manager) +const int Uln::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG = "Uln::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG"c; +const int Uln::OP::SET_DEFAULT_ULN_SEND_CONFIG = "Uln::OP::SET_DEFAULT_ULN_SEND_CONFIG"c; + +const int Uln::OP::SET_OAPP_ULN_RECEIVE_CONFIG = "Uln::OP::SET_OAPP_ULN_RECEIVE_CONFIG"c; +const int Uln::OP::SET_OAPP_ULN_SEND_CONFIG = "Uln::OP::SET_OAPP_ULN_SEND_CONFIG"c; + +const int Uln::OP::UPDATE_WORKER_FEELIB = "Uln::OP::UPDATE_WORKER_FEELIB"c; +const int Uln::OP::REFILL_WORKER_RENT = "Uln::OP::REFILL_WORKER_RENT"c; + +const int Uln::OP::SET_TREASURY_FEE_BPS = "Uln::OP::SET_TREASURY_FEE_BPS"c; + +;; Remove a worker from this ULN +;; only the admin of the registered worker +const int Uln::OP::DEREGISTER_WORKER_FEELIB = "Uln::OP::DEREGISTER_WORKER_FEELIB"c; + +const int Uln::OP::COLLECT_WORKER_RENT = "Uln::OP::COLLECT_WORKER_RENT"c; +const int Uln::OP::GC_ATTESTATIONS = "Uln::OP::GC_ATTESTATIONS"c; + +;; Set a worker configuration +;; only-owner (manager) +const int Uln::OP::SET_WORKER_FEELIB_STORAGE = "Uln::OP::SET_WORKER_FEELIB_STORAGE"c; + +const int Uln::OP::ULN_COMMIT_PACKET = "Uln::OP::ULN_COMMIT_PACKET"c; +const int Uln::OP::ULN_VERIFY = "Uln::OP::ULN_VERIFY"c; + +const int Uln::OP::ULN_QUOTE = "Uln::OP::ULN_QUOTE"c; +const int Uln::OP::ULN_SEND = "Uln::OP::ULN_SEND"c; + +const int Uln::OP::SET_WORKER_FEELIB_STORAGE_CALLBACK = "Uln::OP::SET_WORKER_FEELIB_STORAGE_CALLBACK"c; + +const int Uln::CONST::ADMIN_WORKER_SLOTS = 200; + +const int Uln::ERROR::notUlnConnection = 224; +const int Uln::ERROR::insufficientRent = 225; +const int Uln::ERROR::nonexistentWorker = 226; +const int Uln::ERROR::invalidTimestamp = 227; +const int Uln::ERROR::invalidWorkerStorage = 228; +const int Uln::ERROR::invalidTreasuryFeeBps = 229; +const int UlnWorker::state::active = 1; + +;; this will overflow 256-bit number after 10^64 years +const int Uln::CONST::RENT_NANOS_PER_SECOND = 100 * 1000; +const int Uln::CONST::INITIAL_RENT_NANOS = 3600 * 24 * 30 * Uln::CONST::RENT_NANOS_PER_SECOND; + +const int Uln::event::ULN_WORKER_REGISTERED = "Uln::event::ULN_WRKR_REGISTRD"u; +const int Uln::event::ULN_WORKER_SLOTS_FULL = "Uln::event::ULN_WRKR_SLOTS_FULL"u; +const int Uln::event::ULN_WORKER_DEREGISTERED = "Uln::event::ULN_WRKR_DEREGISTRD"u; +const int Uln::event::ULN_WORKER_EVICTED = "Uln::event::ULN_WORKER_EVICTED"u; +const int Uln::event::ULN_COLLECT_WORKER_RENT = "Uln::event::ULN_COLLECT_WRKR_RNT"u; +const int Uln::event::ULN_WORKER_STORAGE_SET = "Uln::event::ULN_WRKR_STRG_SET"u; +const int Uln::event::ULN_WORKER_RENT_REFILLED = "Uln::event::ULN_WRKR_RNT_REFILL"u; + +const int Uln::event::ULN_DEFAULT_SEND_CONFIG_SET = "Uln::event::ULN_DFLT_SND_CFG_SET"u; +const int Uln::event::ULN_DEFAULT_RECEIVE_CONFIG_SET = "Uln::event::ULN_DFLT_REC_CFG_SET"u; +const int Uln::event::ULN_TREASURY_FEE_BPS_SET = "Uln::event::ULN_TRSR_FEE_BPS_SET"u; + +;; This error code represents a byte flag in a return value, not an actual error. +const int Uln::ErrorCode::WORKER_QUOTE_FAILED = "Uln::ErrorCode::WORKER_QUOTE_FAILED"c & 0xff; + +const int Uln::WorkerFeelibInfo::MaxCells = 12; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/main.fc new file mode 100644 index 00000000..2b9c1224 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/main.fc @@ -0,0 +1,37 @@ +#include "../../interface.fc"; ;; msglib interface +#include "../../../core/abstract/protocolMain.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure { + if (op == Uln::OP::ULN_SEND) { + return ulnSend($md); + } elseif (op == Uln::OP::ULN_COMMIT_PACKET) { + return ulnCommitPacket($md); + } elseif (op == Uln::OP::ULN_VERIFY) { + return ulnVerify($md); + } elseif (op == Uln::OP::ULN_QUOTE) { + return ulnQuote($md); + } elseif (op == Uln::OP::UPDATE_WORKER_FEELIB) { + return updateWorkerFeelib($md); + } elseif (op == Uln::OP::DEREGISTER_WORKER_FEELIB) { + return deregisterWorkerFeelib($md); + } elseif (op == Uln::OP::SET_WORKER_FEELIB_STORAGE) { + return setWorkerFeelibStorage($md); + } elseif (op == Uln::OP::COLLECT_WORKER_RENT) { + return collectWorkerRent($md); + } elseif (op == Uln::OP::GC_ATTESTATIONS) { + return garbageCollectInvalidAttestations($md); + } elseif (op == Uln::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG) { + return setDefaultUlnReceiveConfig($md); + } elseif (op == Uln::OP::SET_DEFAULT_ULN_SEND_CONFIG) { + return setDefaultUlnSendConfig($md); + } elseif (op == Uln::OP::SET_TREASURY_FEE_BPS) { + return setTreasuryFeeBps($md); + } elseif (op == Uln::OP::REFILL_WORKER_RENT) { + return refillWorkerRent($md); + } + throw(BaseInterface::ERROR::invalidOpcode); + return null(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/storage.fc new file mode 100644 index 00000000..ff58035a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/storage.fc @@ -0,0 +1,119 @@ +#include "../msgdata/UlnReceiveConfig.fc"; +#include "../msgdata/UlnSendConfig.fc"; +#include "../../../core/baseStorage.fc"; + +const int Uln::MaxWorkerFeelibs = 1024; + +const int Uln::NAME = "uln"u; + +const int Uln::baseStorage = 0; +const int Uln::eid = 1; +const int Uln::dstEid = 2; +const int Uln::defaultUlnReceiveConfig = 3; +const int Uln::defaultUlnSendConfig = 4; + +const int Uln::connectionCode = 5; + +;; map of address -> object +const int Uln::workerFeelibInfos = 6; + +const int Uln::treasuryFeeBps = 7; +const int Uln::remainingWorkerSlots = 8; +const int Uln::remainingAdminWorkerSlots = 9; + +;; Note that the default receive and send config can be initialized in the constructor unlike +;; ulnConnection which must be done in initialize due to opt-in semantics +;; @owner ulnManager +cell Uln::New(int owner, int eid, int dstEid) impure method_id { + return cl::declare( + Uln::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; Uln::baseStorage + [cl::t::uint32, eid], ;; Uln::eid + [cl::t::uint32, dstEid], ;; Uln::dstEid + [cl::t::objRef, UlnReceiveConfig::NewWithDefaults()], ;; Uln::defaultUlnReceiveConfig + [cl::t::objRef, UlnSendConfig::NewWithDefaults()], ;; Uln::defaultUlnSendConfig + [cl::t::cellRef, empty_cell()], ;; Uln::connectionCode + [cl::t::dict256, cl::dict256::New()], ;; Uln::workerFeelibInfos + [cl::t::uint16, 0], ;; Uln::treasuryFeeBps + [cl::t::uint16, 0], ;; Uln::remainingWorkerSlots + [cl::t::uint16, 0] ;; Uln::remainingAdminWorkerSlots + ]) + ); +} + +;; ============================== Object Getters ============================== + +const int Uln::_eidOffset = _HEADER_WIDTH; +const int Uln::_dstEidOffset = Uln::_eidOffset + 32; +const int Uln::_treasuryFeeBpsOffset = Uln::_dstEidOffset + 32; +const int Uln::_remainingWorkerSlotsOffset = Uln::_treasuryFeeBpsOffset + 16; +const int Uln::_remainingAdminWorkerSlotsOffset = Uln::_remainingWorkerSlotsOffset + 16; +const int Uln::_sliceBits = Uln::_remainingAdminWorkerSlotsOffset + 16; + +cell Uln::getDefaultUlnReceiveConfig(cell $self) impure inline { + return $self.cellPreloadRefAt(1); +} + +cell Uln::getConnectionCode(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(1); +} + +cell Uln::getWorkerFeelibInfos(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(2); +} + +;; ============================== Object Multi-Getters ============================== + +;; (owner, defaultUlnSendConfig) +cell Uln::getDefaultUlnSendConfig(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return selfSlice.preloadRefAt(2).cellPreloadRefAt(0); ;; defaultUlnSendConfig +} + +;; (treasuryFeeBps, owner) +(int, int) Uln::getQuoteWorkersInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadRefAt(0).BaseStorage::getOwner(), ;; baseStorage.Owner + selfSlice.preloadUint16At(Uln::_treasuryFeeBpsOffset) ;; treasuryFeeBps + ); +} + +;; ============================== Object Setters ============================== + +cell Uln::setWorkerFeelibInfos(cell $self, cell $workerFeelibInfos) impure inline { + slice selfSlice = $self.begin_parse(); + + slice ref2_slice = selfSlice.preloadRefSliceAt(2); + cell new_ref2 = begin_cell() + .store_slice(ref2_slice.scutfirst(0, 2)) + .store_ref($workerFeelibInfos) + .end_cell(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(Uln::_sliceBits, 2)) + .store_ref(new_ref2) + .end_cell(); +} + +;; updates the worker feelib info for the given [worker address, new worker feelib info] +;; and returns the new storage +cell Uln::updateWorkerFeelibInfos(cell $self, int workerAddress, cell $workerFeelibInfo) impure inline { + slice selfSlice = $self.begin_parse(); + + slice ref2_slice = selfSlice.preloadRefSliceAt(2); + cell $workerFeelibInfos = ref2_slice + .preloadRefAt(2) + .cl::dict256::setRef(workerAddress, $workerFeelibInfo); + + cell new_ref2 = begin_cell() + .store_slice(ref2_slice.scutfirst(0, 2)) + .store_ref($workerFeelibInfos) + .end_cell(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(Uln::_sliceBits, 2)) + .store_ref(new_ref2) + .end_cell(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib1.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib1.fc new file mode 100644 index 00000000..7c7e7da6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib1.fc @@ -0,0 +1,90 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib1"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will return empty tuple +tuple ulnWorker::quote(tuple inputs) impure method_id { + return unsafeTuple([]); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::returnsEmptyTuple(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::returnsEmptyTuple, "badFeeLib::returnsEmptyTuple"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib10.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib10.fc new file mode 100644 index 00000000..556f1247 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib10.fc @@ -0,0 +1,90 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib10"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will return a tuple where the fee is below 0 +tuple ulnWorker::quote(tuple inputs) impure method_id { + return empty_tuple().tpush(-1).tpush(null()); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::returnsTupleWithNegativeFee(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::returnsTupleWithNegativeFee, "badFeeLib::returnsTupleWithNegativeFee"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib11.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib11.fc new file mode 100644 index 00000000..016fd53b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib11.fc @@ -0,0 +1,90 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib11"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will return empty tuple +tuple ulnWorker::quote(tuple inputs) impure method_id { + return unsafeTuple([1]); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::returnsTooSmallTuple(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::returnsTooSmallTuple, "badFeeLib::returnsTooSmallTuple"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib12.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib12.fc new file mode 100644 index 00000000..e4507ae5 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib12.fc @@ -0,0 +1,87 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib12"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib does not implement the quote method + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::methodNotPresent(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::methodNotPresent, "badFeeLib::methodNotPresent"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib2.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib2.fc new file mode 100644 index 00000000..456593a1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib2.fc @@ -0,0 +1,91 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib2"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will always throw +tuple ulnWorker::quote(tuple inputs) impure method_id { + throw_if(true, "malicious fee lib 2"c); + return unsafeTuple([]); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::throwsException(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::throwsException, "badFeeLib::throwsException"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib3.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib3.fc new file mode 100644 index 00000000..4805c1d0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib3.fc @@ -0,0 +1,96 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib3"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will run out of gas +tuple ulnWorker::quote(tuple inputs) impure method_id { + int maxCounter = 1000 * 1000 * 1000 * 1000; + int counter = 0; + while (counter < maxCounter) { + counter += 1; + } + return unsafeTuple([]); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::runsOutOfGas(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + .tpush([badFeeLib::runsOutOfGas, "badFeeLib::runsOutOfGas"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib4.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib4.fc new file mode 100644 index 00000000..895098a6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib4.fc @@ -0,0 +1,90 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib4"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will return a tuple with three items +tuple ulnWorker::quote(tuple inputs) impure method_id { + return unsafeTuple([1, 2, 3]); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::returnsTooBigTuple(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::returnsTooBigTuple, "badFeeLib::returnsTooBigTuple"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib5.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib5.fc new file mode 100644 index 00000000..feb825dc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib5.fc @@ -0,0 +1,90 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib5"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will return a cell instead of a tuple +cell ulnWorker::quote(tuple inputs) impure method_id { + return _getRandomCode(123); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::returnsCellInsteadOfTuple(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::returnsCellInsteadOfTuple, "badFeeLib::returnsCellInsteadOfTuple"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib6.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib6.fc new file mode 100644 index 00000000..c0f3544a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib6.fc @@ -0,0 +1,91 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib6"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will return a tuple with three items +tuple ulnWorker::quote(tuple inputs) impure method_id { + ;; return empty_tuple().tpush(123).tpush(empty_cell()).tpush(empty_cell()); + return unsafeTuple([123, empty_cell(), empty_cell()]); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::returnsTooBigTupleWithCells(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::returnsTooBigTupleWithCells, "badFeeLib::returnsTooBigTupleWithCells"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib7.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib7.fc new file mode 100644 index 00000000..236c2e3d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib7.fc @@ -0,0 +1,90 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib7"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will return a tuple where the event is not a cell/null +tuple ulnWorker::quote(tuple inputs) impure method_id { + return unsafeTuple([123, empty_tuple()]); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::returnsTupleWithWrongEventType(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::returnsTupleWithWrongEventType, "badFeeLib::returnsTupleWithWrongEventType"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib8.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib8.fc new file mode 100644 index 00000000..36e70b40 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib8.fc @@ -0,0 +1,90 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib8"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will return a tuple where the fee is not an int +tuple ulnWorker::quote(tuple inputs) impure method_id { + return unsafeTuple([empty_tuple(), null()]); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::returnsTupleWithWrongFeeType(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::returnsTupleWithWrongFeeType, "badFeeLib::returnsTupleWithWrongFeeType"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib9.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib9.fc new file mode 100644 index 00000000..76f71308 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib9.fc @@ -0,0 +1,90 @@ +#include "../testutil.fc"; +#include "../../handler.fc"; +#include "../../interface.fc"; +#include "../../../msgdata/UlnSend.fc"; +#include "../../../msgdata/UlnSendConfig.fc"; +#include "../../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../../tests/consts.fc"; +#include "../../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../../tests/testMain.fc"; +#include "../../../../../interfaces.fc"; +#include "../../../../../../funC++/utils.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../../funC++/actions/utils.fc"; + +#include "../../../../../channel/interface.fc"; +#include "../../../../../../funC++/actions/call.fc"; + +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/constants.fc"; + +slice _testName() { return "badFeeLib9"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;; this malicious fee lib will return a tuple where both fee and event are null +tuple ulnWorker::quote(tuple inputs) impure method_id { + return empty_tuple().tpush(null()).tpush(null()); +} + +;;; ===============================TESTS========================================= + +(int, slice) badFeeLib::returnsTupleWithNullFeeAndEvent(cell $storage) impure { + ;; doesn't matter which one we use, the malicious fee lib will try to screw it up + ;; since it's read from my_code in unit tests. + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 0, + 0, + MOCK_LZ_SEND(), + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([badFeeLib::returnsTupleWithNullFeeAndEvent, "badFeeLib::returnsTupleWithNullFeeAndEvent"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/computeSizeGasTest.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/computeSizeGasTest.fc new file mode 100644 index 00000000..0c8fd5d2 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/computeSizeGasTest.fc @@ -0,0 +1,62 @@ +#include "../../../../../core/abstract/protocolHandler.fc"; +#include "../../../../../core/abstract/protocolMain.fc"; +#include "../../../../../../classes/msgdata/MdAddress.fc"; +#include "storage.fc"; + +;;; ==========================OPCODES===================================== + +const int ComputeSizeGasTest::OP::ADD_TO_MOCK_DICT = 111; + +;;; ==========================HELPER FUNCTIONS===================================== + +(cell, tuple) _initialize(cell $md) impure inline { + return preamble(); +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + return (); +} + +;;; ================HANDLER FUNCTIONS===================== + +tuple addToMockDict(cell $mdAddress) impure inline { + cell $storage = getContractStorage(); + int index = $mdAddress.cl::get
(md::MdAddress::address); + cell $md = $mdAddress.cl::get(md::MdAddress::md); + + cell mockDict = $storage + .cl::get(ComputeSizeGasTest::mockDict) + .cl::dict256::setRef(index, $md); + + setContractStorage( + $storage.cl::set(ComputeSizeGasTest::mockDict, mockDict) + ); + + int initGas = get_gas_consumed(); + (int n_cell, int n_bits, int n_refs, int success) = compute_data_size?(mockDict, MAX_U64); + int totalGas = get_gas_consumed() - initGas; + ~dump(totalGas); + ~strdump("number of cells: "); + ~dump(n_cell); + + ~strdump("just to make sure the indexes are different: "); + ~dump(index); + (cell x, int s) = mockDict.cl::dict256::get(index); + return emptyActions(); +} + +;;; ================MAIN FUNCTION===================== + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == ComputeSizeGasTest::OP::ADD_TO_MOCK_DICT) { + return addToMockDict($md); + } + throw(BaseInterface::ERROR::invalidOpcode); + return empty_tuple(); +} + +int _getEventSink() inline { + return getContractAddress(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/storage.fc new file mode 100644 index 00000000..b3f1032d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/storage.fc @@ -0,0 +1,19 @@ +#include "../../../../../core/baseStorage.fc"; + +;; required object name +const int ComputeSizeGasTest::NAME = "cmptGas"u; + +;; field names +const int ComputeSizeGasTest::baseStorage = 0; +const int ComputeSizeGasTest::mockDict = 1; + +;; @owner manager +cell ComputeSizeGasTest::New(int owner) method_id { + return cl::declare( + ComputeSizeGasTest::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; ComputeSizeGasTest::baseStorage + [cl::t::dict256, cl::dict256::New()] ;; ComputeSizeGasTest::mockDict + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/main.fc new file mode 100644 index 00000000..9a46a00f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/main.fc @@ -0,0 +1,1159 @@ +#include "testutil.fc"; + +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../msgdata/Attestation.fc"; +#include "../../msgdata/InitUln.fc"; +#include "../../msgdata/RentRefill.fc"; +#include "../../msgdata/TreasuryFeeBps.fc"; +#include "../../msgdata/UlnReceiveConfig.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnWorkerFeelibInfo.fc"; +#include "../../ulnConnection/interface.fc"; +#include "../../ulnManager/interface.fc"; + +#include "../../../../interfaces.fc"; + +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; + +#include "../../../../../funC++/actions/utils.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../funC++/testutils.fc"; +#include "../../../../../funC++/utils.fc"; + +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; + +slice _testName() { return "uln"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + cell $obj = Uln::New(getCaller(), SRC_EID, DST_EID); + setContractStorage($obj); + return $obj; +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) ulnCommitPacket::success::basic(cell $storage) impure { + return test::handler::shouldPass( + ulnCommitPacket, + md::MdAddress::New( + MOCK_RECEIVE_PACKET(), + ULN_CONNECTION_ADDRESS + ), + unsafeTuple([ + 0, + _newAction( + ULN_CONNECTION_ADDRESS, + UlnConnection::OP::ULN_CONNECTION_COMMIT_PACKET, + md::MdObj::New( + MOCK_RECEIVE_PACKET(), + $storage.cl::get(Uln::defaultUlnReceiveConfig) + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) ulnVerify::success::basic(cell $storage) impure { + cell $attestation = lz::Attestation::New( + PACKET_HASH, DEFAULT_CONFIRMATIONS + ); + spoofCaller(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0)); + return test::handler::shouldPass( + ulnVerify, + md::MdAddress::New( + $attestation, + ULN_CONNECTION_ADDRESS + ), + unsafeTuple([ + 0, + _newAction( + ULN_CONNECTION_ADDRESS, + UlnConnection::OP::ULN_CONNECTION_VERIFY, + md::ExtendedMd::New( + $attestation, + $storage.cl::get(Uln::defaultUlnReceiveConfig), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) garbageCollectInvalidAttestations::success::basic(cell $storage) { + cell $mdAddress = md::MdAddress::New(MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), ULN_CONNECTION_ADDRESS); + + return test::handler::shouldPass( + garbageCollectInvalidAttestations, + $mdAddress, + unsafeTuple([ + 0, + _newAction( + ULN_CONNECTION_ADDRESS, + UlnConnection::OP::GARBAGE_COLLECT_INVALID_ATTESTATIONS, + md::MdObj::New( + $mdAddress.cl::get(md::MdAddress::md), + $storage.cl::get(Uln::defaultUlnReceiveConfig) + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) collectWorkerRent::success::basic(cell $storage) impure { + ;; add worker with sufficient rent for 30 days + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS); + updateWorkerFeelib($workerFeelibInfo); + $storage = getContractStorage(); + + ;; advance 15 days + int fifteenDaysSeconds = 15 * 24 * 60 * 60; + setNewTime(now() + fifteenDaysSeconds); + + ;; build $expectedWorkerInfo and $expectedStorage + int rentOwed = Uln::CONST::RENT_NANOS_PER_SECOND * fifteenDaysSeconds; + int currentRent = $workerFeelibInfo.cl::get(UlnWorkerFeelibInfo::rentBalance); + int expectedRent = currentRent - rentOwed; + + cell $expectedWorkerInfo = $workerFeelibInfo + .cl::set(UlnWorkerFeelibInfo::rentBalance, expectedRent) + .cl::set(UlnWorkerFeelibInfo::lastRentTimestamp, now()); + + cell $expectedStorage = $storage + .cl::nestedDict256::setRef( + Uln::workerFeelibInfos, + $workerFeelibInfo.cl::get
(UlnWorkerFeelibInfo::workerAddress), + $expectedWorkerInfo + ); + + return test::handler::shouldPass( + collectWorkerRent, + $workerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_COLLECT_WORKER_RENT, + $expectedWorkerInfo + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) collectWorkerRent::success::admin(cell $storage) impure { + ;; add worker with sufficient rent for 30 days + cell $workerFeelibInfo = MOCK_ADMIN_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ); + updateWorkerFeelib($workerFeelibInfo); + $storage = getContractStorage(); + + ;; advance 15 days + int fifteenDaysSeconds = 15 * 24 * 60 * 60; + setNewTime(now() + fifteenDaysSeconds); + + return test::handler::shouldPass( + collectWorkerRent, + $workerFeelibInfo, + emptyActions(), + $storage, + txnContext + ); +} + +(int, slice) collectWorkerRent::success::evicted::insufficientRent(cell $storage) impure { + ;; add worker with sufficient rent for 30 days + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS); + updateWorkerFeelib($workerFeelibInfo); + $storage = getContractStorage(); + + ;; advance 31 days + int initialTimestamp = now(); + int 31DaysSeconds = 31 * 24 * 60 * 60; + setNewTime(initialTimestamp + 31DaysSeconds); + + ;; build $expectedWorkerInfo and $expectedStorage from $actualStorage + cell $actualStorage = getContractStorage(); + + (cell $expectedWorkerInfo, _) = $actualStorage.cl::nestedDict256::get( + Uln::workerFeelibInfos, + $workerFeelibInfo.cl::get
(UlnWorkerFeelibInfo::workerAddress) + ); + + cell $expectedStorage = $actualStorage + .cl::nestedDict256::delete( + Uln::workerFeelibInfos, + $workerFeelibInfo.cl::get
(UlnWorkerFeelibInfo::workerAddress) + ) + .cl::set(Uln::remainingWorkerSlots, Uln::MaxWorkerFeelibs - UlnManager::CONST::MAX_ADMIN_WORKERS) + ; + + ;; call collectWorkerRent + return test::handler::shouldPass( + collectWorkerRent, + $workerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_EVICTED, + $expectedWorkerInfo + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) collectWorkerRent::revert::nonexistentWorker(cell $storage) impure { + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS); + + return test::handler::shouldFail( + collectWorkerRent, + $workerFeelibInfo, + Uln::ERROR::nonexistentWorker + ); +} + +(int, slice) refillWorkerRent::success::basic(cell $storage) impure { + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS * 2); + updateWorkerFeelib($workerFeelibInfo); + cell $expectedStorage = getContractStorage(); + setContractStorage($storage); + + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS); + updateWorkerFeelib($workerFeelibInfo); + + cell $rentRefill = md::RentRefill::New( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + Uln::CONST::INITIAL_RENT_NANOS + ); + + return test::handler::shouldPass( + refillWorkerRent, + $rentRefill, + unsafeTuple([ + 0, + _newAction( + getOwner(), + Uln::CONST::INITIAL_RENT_NANOS, + 0 + ), + _newAction( + Uln::event::ULN_WORKER_RENT_REFILLED, + $rentRefill + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) refillWorkerRent::revert::nonexistentWorker(cell $storage) impure { + cell $rentRefill = md::RentRefill::New( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + Uln::CONST::INITIAL_RENT_NANOS + ); + + return test::handler::shouldFail( + refillWorkerRent, + $rentRefill, + Uln::ERROR::nonexistentWorker + ); +} + +(int, slice) updateWorkerFeelib::success::basic(cell $storage) impure { + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS); + + return test::handler::shouldPass( + updateWorkerFeelib, + $workerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + getOwner(), + Uln::CONST::INITIAL_RENT_NANOS, + 0 + ), + _newAction( + Uln::event::ULN_WORKER_REGISTERED, + $workerFeelibInfo.cl::set( + UlnWorkerFeelibInfo::lastRentTimestamp, + now() + ) + ) + ]), + $storage + .cl::nestedDict256::setRef( + Uln::workerFeelibInfos, + $workerFeelibInfo.cl::get
(UlnWorkerFeelibInfo::workerAddress), + $workerFeelibInfo.cl::set( + UlnWorkerFeelibInfo::lastRentTimestamp, + now() + ) + ) + .cl::set( + Uln::remainingWorkerSlots, + Uln::MaxWorkerFeelibs - UlnManager::CONST::MAX_ADMIN_WORKERS - 1 + ), + txnContext + ); +} + +(int, slice) updateWorkerFeelib::success::ignoreRentIfExists(cell $storage) impure { + ;; Test that if a worker cannot change their rent balance when updating an existing worker + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + Uln::CONST::INITIAL_RENT_NANOS + ); + updateWorkerFeelib($workerFeelibInfo); + cell $expectedStorage = getContractStorage(); + + return test::handler::shouldPass( + updateWorkerFeelib, + $workerFeelibInfo.cl::set(UlnWorkerFeelibInfo::rentBalance, MAX_U128), + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_REGISTERED, + $workerFeelibInfo.cl::set(UlnWorkerFeelibInfo::lastRentTimestamp, now()) + ) + ]), + $expectedStorage, + txnContext + ); +} + + +(int, slice) updateWorkerFeelib::success::admin(cell $storage) impure { + cell $workerFeelibInfo = MOCK_ADMIN_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ); + + return test::handler::shouldPass( + updateWorkerFeelib, + $workerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_REGISTERED, + $workerFeelibInfo.cl::set( + UlnWorkerFeelibInfo::lastRentTimestamp, + now() + ) + ) + ]), + $storage + .cl::nestedDict256::setRef( + Uln::workerFeelibInfos, + $workerFeelibInfo.cl::get
(UlnWorkerFeelibInfo::workerAddress), + $workerFeelibInfo.cl::set( + UlnWorkerFeelibInfo::lastRentTimestamp, + now() + ) + ) + .cl::set(Uln::remainingAdminWorkerSlots, UlnManager::CONST::MAX_ADMIN_WORKERS - 1), + txnContext + ); +} + +(int, slice) updateWorkerFeelib::fail::insufficientRent(cell $storage) impure { + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS - 1); + + return test::handler::shouldFail( + updateWorkerFeelib, + $workerFeelibInfo, + Uln::ERROR::insufficientRent + ); +} + +(int, slice) updateWorkerFeelib::fail::storageTooLarge(cell $storage) impure { + cell $workerFeelibInfo = UlnWorkerFeelibInfo::New( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + my_code(), + my_code(), + NULLADDRESS, + DST_EID, + Uln::CONST::INITIAL_RENT_NANOS, + false + ); + + return test::handler::shouldFail( + updateWorkerFeelib, + $workerFeelibInfo, + Uln::ERROR::invalidWorkerStorage + ); +} + + +(int, slice) updateWorkerFeelib::success::workerAlreadyExists(cell $storage) impure { + cell $oldWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS); + updateWorkerFeelib($oldWorkerFeelibInfo); + + cell $newWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO_WITH_FRIEND( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0), + Uln::CONST::INITIAL_RENT_NANOS + ); + + (cell $registeredWorkerInfo, _) = getContractStorage() + .cl::nestedDict256::get( + Uln::workerFeelibInfos, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ); + return test::handler::shouldPass( + updateWorkerFeelib, + $newWorkerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_REGISTERED, + $registeredWorkerInfo.cl::set( + UlnWorkerFeelibInfo::friendWorkerAddress, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0) + ) + ) + ]), + getContractStorage() + .cl::nestedDict256::setRef( + Uln::workerFeelibInfos, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + $registeredWorkerInfo.cl::set( + UlnWorkerFeelibInfo::friendWorkerAddress, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0) + ) + ), + txnContext + ); +} + +(int, slice) updateWorkerFeelib::success::UpdateEvenWhenWorkerSlotsFull(cell $storage) impure { + setContractStorage( + $storage.cl::set(Uln::remainingWorkerSlots, 1) + ); + cell $oldWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS); + updateWorkerFeelib($oldWorkerFeelibInfo); + + cell $newWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO_WITH_FRIEND( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0), + Uln::CONST::INITIAL_RENT_NANOS + ); + + (cell $registeredWorkerFeelibInfo, _) = getContractStorage().cl::nestedDict256::get( + Uln::workerFeelibInfos, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ); + + return test::handler::shouldPass( + updateWorkerFeelib, + $newWorkerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_REGISTERED, + $registeredWorkerFeelibInfo.cl::set( + UlnWorkerFeelibInfo::friendWorkerAddress, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0) + ) + ) + ]), + getContractStorage() + .cl::nestedDict256::setRef( + Uln::workerFeelibInfos, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + $registeredWorkerFeelibInfo.cl::set( + UlnWorkerFeelibInfo::friendWorkerAddress, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0) + ) + ), + txnContext + ); +} + +(int, slice) updateWorkerFeelib::success::CantAddWorkerWhenSlotsFull(cell $storage) impure { + setContractStorage( + $storage.cl::set(Uln::remainingWorkerSlots, 1) + ); + cell $oldWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS); + updateWorkerFeelib($oldWorkerFeelibInfo); + + cell $newWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO(MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0), Uln::CONST::INITIAL_RENT_NANOS); + + return test::handler::shouldPass( + updateWorkerFeelib, + $newWorkerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_SLOTS_FULL, + $newWorkerFeelibInfo + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) deregisterWorkerFeelib::success::basic(cell $storage) impure { + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + Uln::CONST::INITIAL_RENT_NANOS + ); + updateWorkerFeelib($workerFeelibInfo); + $storage = getContractStorage(); + + (cell $workerFeelibInfo, _) = $storage + .cl::nestedDict256::get( + Uln::workerFeelibInfos, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ); + + spoofCaller(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0)); + return test::handler::shouldPass( + deregisterWorkerFeelib, + cl::nullObject(), + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_DEREGISTERED, + $workerFeelibInfo + ) + ]), + $storage + .cl::nestedDict256::delete( + Uln::workerFeelibInfos, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + .cl::set( + Uln::remainingWorkerSlots, + $storage.cl::get(Uln::remainingWorkerSlots) + 1 + ) + , + txnContext + ); +} + +(int, slice) deregisterWorkerFeelib::success::admin(cell $storage) impure { + cell $workerFeelibInfo = MOCK_ADMIN_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ); + updateWorkerFeelib($workerFeelibInfo); + $storage = getContractStorage(); + + (cell $workerFeelibInfo, _) = $storage + .cl::nestedDict256::get( + Uln::workerFeelibInfos, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ); + + spoofCaller(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0)); + return test::handler::shouldPass( + deregisterWorkerFeelib, + cl::nullObject(), + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_DEREGISTERED, + $workerFeelibInfo + ) + ]), + $storage + .cl::nestedDict256::delete( + Uln::workerFeelibInfos, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + .cl::set( + Uln::remainingAdminWorkerSlots, + $storage.cl::get(Uln::remainingAdminWorkerSlots) + 1 + ) + , + txnContext + ); +} + +(int, slice) deregisterWorkerFeelib::success::nonexistentWorker(cell $storage) impure { + cell $workerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO( + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), + Uln::CONST::INITIAL_RENT_NANOS + ); + + updateWorkerFeelib($workerFeelibInfo); + cell expectedStorage = getContractStorage(); + + spoofCaller(ARBITRARY_ADDRESS); + return test::handler::shouldPass( + deregisterWorkerFeelib, + cl::nullObject(), + unsafeTuple([ + 0 + ]), + expectedStorage, + txnContext + ); +} + +(int, slice) setDefaultUlnSendConfig::success::basic(cell $storage) impure { + cell $ulnSendConfig = MOCK_DEFAULT_ULN_SEND_CONFIG(); + return test::handler::shouldPass( + setDefaultUlnSendConfig, + $ulnSendConfig, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_DEFAULT_SEND_CONFIG_SET, + $ulnSendConfig + ) + ]), + $storage.cl::set(Uln::defaultUlnSendConfig, $ulnSendConfig), + txnContext + ); +} + +(int, slice) setDefaultUlnSendConfig::success::malicious(cell $storage) impure { + cell $ulnSendConfig = MOCK_DEFAULT_ULN_SEND_CONFIG(); + cell $maliciousUlnSendConfig = _dupWithGarbage($ulnSendConfig); + return test::handler::shouldPass( + setDefaultUlnSendConfig, + $maliciousUlnSendConfig, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_DEFAULT_SEND_CONFIG_SET, + $ulnSendConfig + ) + ]), + $storage.cl::set(Uln::defaultUlnSendConfig, $ulnSendConfig), + txnContext + ); +} +(int, slice) setDefaultUlnSendConfig::fail::nullAddress::dvnConfigError(cell $storage) impure { + cell $ulnSendConfig = UlnSendConfig::New( + DEFAULT_WORKER_QUOTE_GAS_LIMIT, + DEFAULT_MAX_MESSAGE_BYTES, + true, + DEFAULT_EXECUTOR, + true, + MOCK_DVN_CELL_WITH_NULL(2), + true, + MOCK_DEFAULT_OPTIONAL_DVN_CELL(2, true), + true, + DEFAULT_CONFIRMATIONS + ); + + return test::handler::shouldFail( + setDefaultUlnSendConfig, + $ulnSendConfig, + UlnSendConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setDefaultUlnSendConfig::fail::maxDVNCount::dvnConfigError(cell $storage) impure { + cell $ulnSendConfig = UlnSendConfig::New( + DEFAULT_WORKER_QUOTE_GAS_LIMIT, + DEFAULT_MAX_MESSAGE_BYTES, + true, + DEFAULT_EXECUTOR, + true, + MOCK_DEFAULT_REQUIRED_DVN_CELL(256, MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0)), + true, + MOCK_DEFAULT_OPTIONAL_DVN_CELL(3, 2), + true, + DEFAULT_CONFIRMATIONS + ); + + return test::handler::shouldFail( + setDefaultUlnSendConfig, + $ulnSendConfig, + UlnSendConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setDefaultUlnSendConfig::fail::tooManyRefs::dvnConfigError(cell $storage) impure { + cell $ulnSendConfig = UlnSendConfig::New( + DEFAULT_WORKER_QUOTE_GAS_LIMIT, + DEFAULT_MAX_MESSAGE_BYTES, + true, + DEFAULT_EXECUTOR, + true, + MOCK_DVN_LIST_WITH_REF(2), + true, + MOCK_DEFAULT_OPTIONAL_DVN_CELL(2, 3), + true, + DEFAULT_CONFIRMATIONS + ); + + return test::handler::shouldFail( + setDefaultUlnSendConfig, + $ulnSendConfig, + UlnSendConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setDefaultUlnReceiveConfig::success::basic(cell $storage) impure { + cell $ulnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + return test::handler::shouldPass( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_DEFAULT_RECEIVE_CONFIG_SET, + $ulnReceiveConfig + ) + ]), + $storage.cl::set(Uln::defaultUlnReceiveConfig, $ulnReceiveConfig), + txnContext + ); +} + +(int, slice) setDefaultUlnReceiveConfig::success::maxRequiredDVNs(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(UlnReceiveConfig::MaxRequiredDVNs, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldPass( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_DEFAULT_RECEIVE_CONFIG_SET, + $ulnReceiveConfig + ) + ]), + $storage.cl::set(Uln::defaultUlnReceiveConfig, $ulnReceiveConfig), + txnContext + ); +} + +(int, slice) setDefaultUlnReceiveConfig::success::maxOptionalDVNs(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(UlnReceiveConfig::MaxOptionalDVNs, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + UlnReceiveConfig::MaxOptionalDVNs + ); + + return test::handler::shouldPass( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_DEFAULT_RECEIVE_CONFIG_SET, + $ulnReceiveConfig + ) + ]), + $storage.cl::set(Uln::defaultUlnReceiveConfig, $ulnReceiveConfig), + txnContext + ); +} + +(int, slice) setDefaultUlnReceiveConfig::success::maxDVNs(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(UlnReceiveConfig::MaxRequiredDVNs, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(UlnReceiveConfig::MaxOptionalDVNs, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + UlnReceiveConfig::MaxOptionalDVNs + ); + return test::handler::shouldPass( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_DEFAULT_RECEIVE_CONFIG_SET, + $ulnReceiveConfig + ) + ]), + $storage.cl::set(Uln::defaultUlnReceiveConfig, $ulnReceiveConfig), + txnContext + ); +} + +(int, slice) setDefaultUlnReceiveConfig::success::malicious(cell $storage) impure { + cell $ulnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + cell $maliciousUlnReceiveConfig = _dupWithGarbage($ulnReceiveConfig); + return test::handler::shouldPass( + setDefaultUlnReceiveConfig, + $maliciousUlnReceiveConfig, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_DEFAULT_RECEIVE_CONFIG_SET, + $ulnReceiveConfig + ) + ]), + $storage.cl::set(Uln::defaultUlnReceiveConfig, $ulnReceiveConfig), + txnContext + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::zeroMinCommitPacketGas(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + 0, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::INVALID_MIN_COMMIT_PACKET_GAS + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::minCommitPacketGasTooHigh(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + UlnReceiveConfig::MaxCommitPacketValueAssertion() + 1, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::INVALID_MIN_COMMIT_PACKET_GAS + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::optionalThresholdTooLarge(cell $storage) impure { + cell $ulnReceiveConfig = MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 1, 2); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LARGE + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::optionalThresholdTooLow(cell $storage) impure { + cell $ulnReceiveConfig = MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 2, 0); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LOW + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::dvnCountsAllNil(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::DVN_COUNTS_ALL_NIL + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::nullRequiredDVN::dvnConfigError(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + MOCK_DVN_CELL_WITH_NULL(2), + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::nullOptionalDVN::dvnConfigError(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + false, + MOCK_DVN_CELL_WITH_NULL(2), + 0 + ); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::tooManyRefsRequiredDvns::dvnConfigError(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + MOCK_DVN_LIST_WITH_REF(2), + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::tooManyRefsOptionalDvns::dvnConfigError(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + MOCK_DVN_LIST_WITH_REF(2), + 0 + ); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::maxRequiredDVNCount::dvnConfigError(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(UlnReceiveConfig::MaxRequiredDVNs + 1, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setDefaultUlnReceiveConfig::fail::maxOptionalDVNCount::dvnConfigError(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(1, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(UlnReceiveConfig::MaxOptionalDVNs + 1, MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldFail( + setDefaultUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setTreasuryFeeBps::success::basic(cell $storage) impure { + int bps = TREASURY_FEE_BPS; + cell $treasuryFeeBps = md::TreasuryFeeBps::New(bps); + return test::handler::shouldPass( + setTreasuryFeeBps, + $treasuryFeeBps, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_TREASURY_FEE_BPS_SET, + $treasuryFeeBps + ) + ]), + $storage.cl::set(Uln::treasuryFeeBps, bps), + txnContext + ); +} + +(int, slice) setTreasuryFeeBps::success::maxBps(cell $storage) impure { + int bps = 10000; + cell $treasuryFeeBps = md::TreasuryFeeBps::New(bps); + return test::handler::shouldPass( + setTreasuryFeeBps, + $treasuryFeeBps, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_TREASURY_FEE_BPS_SET, + $treasuryFeeBps + ) + ]), + $storage.cl::set(Uln::treasuryFeeBps, bps), + txnContext + ); +} + +(int, slice) setTreasuryFeeBps::success::minBps(cell $storage) impure { + int bps = 0; + cell $treasuryFeeBps = md::TreasuryFeeBps::New(bps); + return test::handler::shouldPass( + setTreasuryFeeBps, + $treasuryFeeBps, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_TREASURY_FEE_BPS_SET, + $treasuryFeeBps + ) + ]), + $storage.cl::set(Uln::treasuryFeeBps, bps), + txnContext + ); +} + +(int, slice) setTreasuryFeeBps::revert::invalidBps(cell $storage) impure { + int bps = 10001; + cell $treasuryFeeBps = md::TreasuryFeeBps::New(bps); + return test::handler::shouldFail( + setTreasuryFeeBps, + $treasuryFeeBps, + Uln::ERROR::invalidTreasuryFeeBps + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([ulnCommitPacket::success::basic, "ulnCommitPacket::success::basic"]) + .tpush([ulnVerify::success::basic, "ulnVerify::success::basic"]) + ;; -- collectWorkerRent handler tests + .tpush([collectWorkerRent::success::basic, "collectWorkerRent::success::basic"]) + .tpush([collectWorkerRent::success::admin, "collectWorkerRent::success::admin"]) + .tpush([collectWorkerRent::success::evicted::insufficientRent, "collectWorkerRent::success::evicted::insufficientRent"]) + .tpush([collectWorkerRent::revert::nonexistentWorker, "collectWorkerRent::revert::nonexistentWorker"]) + ;; -- refillWorkerRent handler tests + .tpush([refillWorkerRent::success::basic, "refillWorkerRent::success::basic"]) + .tpush([refillWorkerRent::revert::nonexistentWorker, "refillWorkerRent::revert::nonexistentWorker"]) + ;; -- updateWorkerFeelib handler tests + .tpush([updateWorkerFeelib::success::basic, "updateWorkerFeelib::success::basic"]) + .tpush([updateWorkerFeelib::success::ignoreRentIfExists, "updateWorkerFeelib::success::ignoreRentIfExists"]) + .tpush([updateWorkerFeelib::success::admin, "updateWorkerFeelib::success::admin"]) + .tpush([updateWorkerFeelib::fail::insufficientRent, "updateWorkerFeelib::fail::insufficientRent"]) + .tpush([updateWorkerFeelib::fail::storageTooLarge, "updateWorkerFeelib::fail::storageTooLarge"]) + .tpush([updateWorkerFeelib::success::workerAlreadyExists, "updateWorkerFeelib::success::workerAlreadyExists"]) + .tpush([updateWorkerFeelib::success::UpdateEvenWhenWorkerSlotsFull, "updateWorkerFeelib::success::UpdateEvenWhenWorkerSlotsFull"]) + .tpush([updateWorkerFeelib::success::CantAddWorkerWhenSlotsFull, "updateWorkerFeelib::success::CantAddWorkerWhenSlotsFull"]) + ;; -- deregisterWorkerFeelib handler tests + .tpush([deregisterWorkerFeelib::success::basic, "deregisterWorkerFeelib::success::basic"]) + .tpush([deregisterWorkerFeelib::success::admin, "deregisterWorkerFeelib::success::admin"]) + .tpush([deregisterWorkerFeelib::success::nonexistentWorker, "deregisterWorkerFeelib::success::nonexistentWorker"]) + ;; -- setDefaultUlnSendConfig handler tests + .tpush([setDefaultUlnSendConfig::success::basic, "setDefaultUlnSendConfig::success::basic"]) + .tpush([setDefaultUlnSendConfig::success::malicious, "setDefaultUlnSendConfig::success::malicious"]) + .tpush([setDefaultUlnSendConfig::fail::nullAddress::dvnConfigError, "setDefaultUlnSendConfig::fail::nullAddress::dvnConfigError"]) + .tpush([setDefaultUlnSendConfig::fail::tooManyRefs::dvnConfigError, "setDefaultUlnSendConfig::fail::tooManyRefs::dvnConfigError"]) + .tpush([setDefaultUlnSendConfig::fail::maxDVNCount::dvnConfigError, "setDefaultUlnSendConfig::fail::maxDVNCount::dvnConfigError"]) + ;; -- setDefaultUlnReceiveConfig handler tests + .tpush([setDefaultUlnReceiveConfig::success::basic, "setDefaultUlnReceiveConfig::success::basic"]) + .tpush([setDefaultUlnReceiveConfig::success::maxRequiredDVNs, "setDefaultUlnReceiveConfig::success::maxRequiredDVNs"]) + .tpush([setDefaultUlnReceiveConfig::success::maxOptionalDVNs, "setDefaultUlnReceiveConfig::success::maxOptionalDVNs"]) + .tpush([setDefaultUlnReceiveConfig::success::maxDVNs, "setDefaultUlnReceiveConfig::success::maxDVNs"]) + .tpush([setDefaultUlnReceiveConfig::success::malicious, "setDefaultUlnReceiveConfig::success::malicious"]) + .tpush([setDefaultUlnReceiveConfig::fail::zeroMinCommitPacketGas, "setDefaultUlnReceiveConfig::fail::zeroMinCommitPacketGas"]) + .tpush([setDefaultUlnReceiveConfig::fail::minCommitPacketGasTooHigh, "setDefaultUlnReceiveConfig::fail::minCommitPacketGasTooHigh"]) + .tpush([setDefaultUlnReceiveConfig::fail::optionalThresholdTooLarge, "setDefaultUlnReceiveConfig::fail::optionalThresholdTooLarge"]) + .tpush([setDefaultUlnReceiveConfig::fail::optionalThresholdTooLow, "setDefaultUlnReceiveConfig::fail::optionalThresholdTooLow"]) + .tpush([setDefaultUlnReceiveConfig::fail::dvnCountsAllNil, "setDefaultUlnReceiveConfig::fail::dvnCountsAllNil"]) + .tpush([setDefaultUlnReceiveConfig::fail::nullRequiredDVN::dvnConfigError, "setDefaultUlnReceiveConfig::fail::nullRequiredDVN::dvnConfigError"]) + .tpush([setDefaultUlnReceiveConfig::fail::nullOptionalDVN::dvnConfigError, "setDefaultUlnReceiveConfig::fail::nullOptionalDVN::dvnConfigError"]) + .tpush([setDefaultUlnReceiveConfig::fail::tooManyRefsRequiredDvns::dvnConfigError, "setDefaultUlnReceiveConfig::fail::tooManyRefsRequiredDvns::dvnConfigError"]) + .tpush([setDefaultUlnReceiveConfig::fail::tooManyRefsOptionalDvns::dvnConfigError, "setDefaultUlnReceiveConfig::fail::tooManyRefsOptionalDvns::dvnConfigError"]) + .tpush([setDefaultUlnReceiveConfig::fail::maxRequiredDVNCount::dvnConfigError, "setDefaultUlnReceiveConfig::fail::maxRequiredDVNCount::dvnConfigError"]) + .tpush([setDefaultUlnReceiveConfig::fail::maxOptionalDVNCount::dvnConfigError, "setDefaultUlnReceiveConfig::fail::maxOptionalDVNCount::dvnConfigError"]) + ;; -- garbageCollectInvalidAttestations handler tests + .tpush([garbageCollectInvalidAttestations::success::basic, "garbageCollectInvalidAttestations::success::basic"]) + ;; -- setTreasuryFeeBps handler tests + .tpush([setTreasuryFeeBps::success::basic, "setTreasuryFeeBps::success::basic"]) + .tpush([setTreasuryFeeBps::success::maxBps, "setTreasuryFeeBps::success::maxBps"]) + .tpush([setTreasuryFeeBps::success::minBps, "setTreasuryFeeBps::success::minBps"]) + .tpush([setTreasuryFeeBps::revert::invalidBps, "setTreasuryFeeBps::revert::invalidBps"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/management.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/management.fc new file mode 100644 index 00000000..9d48579a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/management.fc @@ -0,0 +1,179 @@ +#include "testutil.fc"; +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../msgdata/UlnWorkerFeelibInfo.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../workerFeeLibs/priceFeedFeeLib/storage.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../funC++/txnContext.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../msgdata/Attestation.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/call.fc"; +#include "../../../../../funC++/actions/event.fc"; +#include "../../../../../funC++/actions/utils.fc"; + +slice _testName() { return "ulnMgmt"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + cell $obj = Uln::New(getCaller(), SRC_EID, DST_EID); + setContractStorage($obj); + return $obj; +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) setWorkerFeelibStorage::success::basic(cell $storage) impure { + updateWorkerFeelib( + MOCK_ULN_WORKER_FEELIB_INFO(WORKER_ADDRESS, Uln::CONST::INITIAL_RENT_NANOS) + ); + $storage = getContractStorage(); + + cell $newStorage = PriceFeedFeelib::New( + DEFAULT_PRICE_RATIO, + DEFAULT_GAS_PRICE_IN_UNIT, + DEFAULT_GAS_PER_BYTE, + DEFAULT_NATIVE_PRICE_USD, + cl::nullObject(), + cl::nullObject() + ); + + cell $expectedWorkerInfo = MOCK_ULN_WORKER_FEELIB_INFO(WORKER_ADDRESS, Uln::CONST::INITIAL_RENT_NANOS) + .cl::set(UlnWorkerFeelibInfo::lastRentTimestamp, now()) + .cl::set(UlnWorkerFeelibInfo::workerFeelibStorage, $newStorage); + + + spoofCaller(WORKER_ADDRESS); + return test::handler::shouldPass( + setWorkerFeelibStorage, + $newStorage, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_STORAGE_SET, + $expectedWorkerInfo + ), + _newAction( + WORKER_ADDRESS, + Uln::OP::SET_WORKER_FEELIB_STORAGE_CALLBACK, + $newStorage + ) + ]), + $storage.cl::nestedDict256::setRef( + Uln::workerFeelibInfos, + WORKER_ADDRESS, + $expectedWorkerInfo + ), + txnContext + ); +} + +(int, slice) setWorkerFeelibStorage::fail::storageTooBig(cell $storage) impure { + updateWorkerFeelib( + MOCK_ULN_WORKER_FEELIB_INFO(WORKER_ADDRESS, Uln::CONST::INITIAL_RENT_NANOS) + ); + $storage = getContractStorage(); + + ;; 3 inner nodes + 10 cell refs = 13 cells > Uln::WorkerFeelibInfo::MaxCells (= 12) + cell $badStorage = cl::declare( + "badStore"u, + unsafeTuple([ + [cl::t::cellRef, begin_cell().store_uint32(1).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(2).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(3).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(4).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(5).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(6).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(7).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(8).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(9).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(10).end_cell()] + ]) + ); + + spoofCaller(WORKER_ADDRESS); + return test::handler::shouldFail( + setWorkerFeelibStorage, + $badStorage, + Uln::ERROR::invalidWorkerStorage + ); +} + +(int, slice) setWorkerFeelibStorage::success::maxSizeStorage(cell $storage) impure { + updateWorkerFeelib( + MOCK_ULN_WORKER_FEELIB_INFO(WORKER_ADDRESS, Uln::CONST::INITIAL_RENT_NANOS) + ); + $storage = getContractStorage(); + + ;; 3 inner nodes + 8 cell refs = 11 cells == Uln::WorkerFeelibInfo::MaxCells (= 12) + cell $newStorage = cl::declare( + "bigStore"u, + unsafeTuple([ + [cl::t::cellRef, begin_cell().store_uint32(1).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(2).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(3).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(4).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(5).end_cell()], + [cl::t::cellRef, begin_cell().store_uint32(6).end_cell()] + ]) + ); + + cell $expectedWorkerInfo = MOCK_ULN_WORKER_FEELIB_INFO(WORKER_ADDRESS, Uln::CONST::INITIAL_RENT_NANOS) + .cl::set(UlnWorkerFeelibInfo::lastRentTimestamp, now()) + .cl::set(UlnWorkerFeelibInfo::workerFeelibStorage, $newStorage); + + spoofCaller(WORKER_ADDRESS); + + return test::handler::shouldPass( + setWorkerFeelibStorage, + $newStorage, + unsafeTuple([ + 0, + _newAction( + Uln::event::ULN_WORKER_STORAGE_SET, + $expectedWorkerInfo + ), + _newAction( + WORKER_ADDRESS, + Uln::OP::SET_WORKER_FEELIB_STORAGE_CALLBACK, + $newStorage + ) + ]), + $storage.cl::nestedDict256::setRef( + Uln::workerFeelibInfos, + WORKER_ADDRESS, + $expectedWorkerInfo + ), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([setWorkerFeelibStorage::success::basic, "setWorkerFeelibStorage::success::basic"]) + .tpush([setWorkerFeelibStorage::success::maxSizeStorage, "setWorkerFeelibStorage::success::maxSizeStorage"]) + .tpush([setWorkerFeelibStorage::fail::storageTooBig, "setWorkerFeelibStorage::fail::storageTooBig"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/mockWorker.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/mockWorker.fc new file mode 100644 index 00000000..8967a9c6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/mockWorker.fc @@ -0,0 +1,12 @@ +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/stdlib.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/LzSend.fc"; + +tuple ulnWorker::quote(tuple inputs) impure method_id(107686) { + return unsafeTuple( + [NATIVE_FEE, MOCK_ULN_WORKER_EVENT()] + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/permissions.fc new file mode 100644 index 00000000..b6c9d410 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/permissions.fc @@ -0,0 +1,254 @@ +#include "testutil.fc"; +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../msgdata/Attestation.fc"; +#include "../../msgdata/RentRefill.fc"; +#include "../../msgdata/TreasuryFeeBps.fc"; +#include "../../msgdata/UlnReceiveConfig.fc"; +#include "../../../../../funC++/stdlib.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/utils.fc"; + +slice _testName() { return "ulnPermissions"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + cell $obj = Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID); + setContractStorage($obj); + return $obj; +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + + +;;; ===============================TESTS========================================= + +(int, slice) checkPermissions::ulnSend::success::basic(cell $storage) impure { + cell ulnConnectionInitStorage = UlnConnection::New( + getOwner(), + MOCK_SEND_PATH(), + ULN_ADDRESS + ); + int ulnConnectionAddress = computeContractAddress( + ulnConnectionInitStorage, + MOCK_ULN_CONNECTION_CODE() + ); + + spoofCaller(ulnConnectionAddress); + + return test::permissions::shouldPass( + Uln::OP::ULN_SEND, + MOCK_ULN_SEND() + ); +} + +(int, slice) checkPermissions::ulnSend::revert::wrongUlnManager(cell $storage) impure { + cell $maliciousUlnConnectionInitStorage = UlnConnection::New( + ATTACKER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ); + int ulnConnectionAddress = computeContractAddress( + $maliciousUlnConnectionInitStorage, + MOCK_ULN_CONNECTION_CODE() + ); + + ;; the caller is a valid Uln connection but owned by a malicious attacker manager + spoofCaller(ulnConnectionAddress); + + return test::permissions::shouldFail( + Uln::OP::ULN_SEND, + MOCK_ULN_SEND() + ); +} + +(int, slice) checkPermissions::ulnSend::revert::notUlnChannel(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Uln::OP::ULN_SEND, + MOCK_ULN_SEND() + ); +} + +(int, slice) checkPermissions::ulnCommitVerification::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + + return test::permissions::shouldPass( + Uln::OP::ULN_COMMIT_PACKET, + md::MdAddress::New( + MOCK_RECEIVE_PACKET(), + ULN_CONNECTION_ADDRESS + ) + ); +} + +(int, slice) checkPermissions::ulnVerify::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + + return test::permissions::shouldPass( + Uln::OP::ULN_VERIFY, + md::MdAddress::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + ULN_CONNECTION_ADDRESS + ) + ); +} + +(int, slice) checkPermissions::ulnQuote::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + + return test::permissions::shouldPass( + Uln::OP::ULN_QUOTE, + MOCK_ULN_SEND() + ); +} + +(int, slice) checkPermissions::deregisterWorkerFeelib::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + Uln::OP::DEREGISTER_WORKER_FEELIB, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::collectWorkerRent::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + Uln::OP::COLLECT_WORKER_RENT, + md::SetAddress::New(ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::setWorkerFeelibStorage::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + Uln::OP::SET_WORKER_FEELIB_STORAGE, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setDefaultUlnReceiveConfig::success::basic(cell $storage) impure { + spoofCaller(ULN_MANAGER_ADDRESS); + return test::permissions::shouldPass( + Uln::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG, + UlnReceiveConfig::NewWithDefaults() + ); +} + +(int, slice) checkPermissions::setDefaultUlnReceiveConfig::revert::notManager(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Uln::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG, + UlnReceiveConfig::NewWithDefaults() + ); +} + +(int, slice) checkPermissions::setDefaultUlnSendConfig::success::basic(cell $storage) impure { + spoofCaller(ULN_MANAGER_ADDRESS); + return test::permissions::shouldPass( + Uln::OP::SET_DEFAULT_ULN_SEND_CONFIG, + UlnSendConfig::NewWithDefaults() + ); +} + +(int, slice) checkPermissions::setDefaultUlnSendConfig::revert::notManager(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Uln::OP::SET_DEFAULT_ULN_SEND_CONFIG, + UlnSendConfig::NewWithDefaults() + ); +} + +(int, slice) checkPermissions::updateWorkerFeelib::success::basic(cell $storage) impure { + spoofCaller(ULN_MANAGER_ADDRESS); + return test::permissions::shouldPass( + Uln::OP::UPDATE_WORKER_FEELIB, + MOCK_ULN_WORKER_FEELIB_INFO(ARBITRARY_ADDRESS, 0) + ); +} + +(int, slice) checkPermissions::updateWorkerFeelib::revert::notUlnManager(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Uln::OP::UPDATE_WORKER_FEELIB, + MOCK_ULN_WORKER_FEELIB_INFO(ARBITRARY_ADDRESS, 0) + ); +} + +(int, slice) checkPermissions::setTreasuryFeeBps::success::basic(cell $storage) impure { + spoofCaller(ULN_MANAGER_ADDRESS); + return test::permissions::shouldPass( + Uln::OP::SET_TREASURY_FEE_BPS, + md::TreasuryFeeBps::New(TREASURY_FEE_BPS) + ); +} + +(int, slice) checkPermissions::setTreasuryFeeBps::revert::notUlnManager(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Uln::OP::SET_TREASURY_FEE_BPS, + md::TreasuryFeeBps::New(TREASURY_FEE_BPS) + ); +} + +(int, slice) checkPermissions::refillWorkerRent::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + Uln::OP::REFILL_WORKER_RENT, + md::RentRefill::New(ARBITRARY_ADDRESS, RENT_NANOS) + ); +} + +(int, slice) checkPermissions::garbageCollectInvalidAttestations::success::basic(cell $storage) impure { + return test::permissions::shouldPass( + Uln::OP::GC_ATTESTATIONS, + md::MdAddress::New(MOCK_SEND_PATH(), DST_EID) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([checkPermissions::ulnSend::success::basic, "checkPermissions::ulnSend::success::basic"]) + .tpush([checkPermissions::ulnSend::revert::wrongUlnManager, "checkPermissions::ulnSend::revert::wrongUlnManager"]) + .tpush([checkPermissions::ulnSend::revert::notUlnChannel, "checkPermissions::ulnSend::revert::notUlnChannel"]) + .tpush([checkPermissions::ulnVerify::success::basic, "checkPermissions::ulnVerify::success::basic"]) + .tpush([checkPermissions::ulnQuote::success::basic, "checkPermissions::ulnQuote::success::basic"]) + .tpush([checkPermissions::ulnCommitVerification::success::basic, "checkPermissions::ulnCommitVerification::success::basic"]) + .tpush([checkPermissions::deregisterWorkerFeelib::success::basic, "checkPermissions::deregisterWorkerFeelib::success::basic"]) + .tpush([checkPermissions::collectWorkerRent::success::basic, "checkPermissions::collectWorkerRent::success::basic"]) + .tpush([checkPermissions::setWorkerFeelibStorage::success::basic, "checkPermissions::setWorkerFeelibStorage::success::basic"]) + .tpush([checkPermissions::setDefaultUlnReceiveConfig::success::basic, "checkPermissions::setDefaultUlnReceiveConfig::success::basic"]) + .tpush([checkPermissions::setDefaultUlnReceiveConfig::revert::notManager, "checkPermissions::setDefaultUlnReceiveConfig::revert::notManager"]) + .tpush([checkPermissions::setDefaultUlnSendConfig::success::basic, "checkPermissions::setDefaultUlnSendConfig::success::basic"]) + .tpush([checkPermissions::setDefaultUlnSendConfig::revert::notManager, "checkPermissions::setDefaultUlnSendConfig::revert::notManager"]) + .tpush([checkPermissions::updateWorkerFeelib::success::basic, "checkPermissions::updateWorkerFeelib::success::basic"]) + .tpush([checkPermissions::updateWorkerFeelib::revert::notUlnManager, "checkPermissions::updateWorkerFeelib::revert::notUlnManager"]) + .tpush([checkPermissions::setTreasuryFeeBps::success::basic, "checkPermissions::setTreasuryFeeBps::success::basic"]) + .tpush([checkPermissions::setTreasuryFeeBps::revert::notUlnManager, "checkPermissions::setTreasuryFeeBps::revert::notUlnManager"]) + .tpush([checkPermissions::refillWorkerRent::success::basic, "checkPermissions::refillWorkerRent::success::basic"]) + .tpush([checkPermissions::garbageCollectInvalidAttestations::success::basic, "checkPermissions::garbageCollectInvalidAttestations::success::basic"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/serde.fc new file mode 100644 index 00000000..4d9081a5 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/serde.fc @@ -0,0 +1,133 @@ +#include "../storage.fc"; +#include "../../../../../../tests/mocks.fc"; + +#include "../../../../../../tests/baseSerdeTest.fc"; +#include "../../../../../../tests/testMain.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "ULN Serde"; } + +;;; ===============================TESTS========================================= + +;; Uln Storage has: 3 Getters +;; 2 multi-getters +;; 2 setters +(int, slice) Serde::Uln::getDefaultUlnReceiveConfig(cell $unused) impure { + cell $UlnStorage = Uln::New( + ULN_MANAGER_ADDRESS, + SRC_EID, + DST_EID + ); + + return test::getRef::equal( + $UlnStorage, + Uln::getDefaultUlnReceiveConfig, + Uln::defaultUlnReceiveConfig + ); +} + +(int, slice) Serde::Uln::getConnectionCode(cell $unused) impure { + cell $UlnStorage = Uln::New( + ULN_MANAGER_ADDRESS, + SRC_EID, + DST_EID + ).cl::set(Uln::connectionCode, _getRandomCode(55)); + + return test::getRef::equal( + $UlnStorage, + Uln::getConnectionCode, + Uln::connectionCode + ); +} + +(int, slice) Serde::Uln::getWorkerFeelibInfos(cell $unused) impure { + cell $UlnStorage = Uln::New( + ULN_MANAGER_ADDRESS, + SRC_EID, + DST_EID + ).cl::set(Uln::workerFeelibInfos, _getRandomCode(56)); + + return test::getRef::equal( + $UlnStorage, + Uln::getWorkerFeelibInfos, + Uln::workerFeelibInfos + ); +} + +(int, slice) Serde::Uln::getDefaultUlnSendConfig(cell $unused) impure { + cell $UlnStorage = Uln::New( + ULN_MANAGER_ADDRESS, + SRC_EID, + DST_EID + ); + + return test::getRef::equal( + $UlnStorage, + Uln::getDefaultUlnSendConfig, + Uln::defaultUlnSendConfig + ); +} + +(int, slice) Serde::Uln::getQuoteWorkersInformation(cell $unused) impure { + cell $UlnStorage = Uln::New( + ULN_MANAGER_ADDRESS, + SRC_EID, + DST_EID + ).cl::set(Uln::treasuryFeeBps, 500); + + int expectedOwner = $UlnStorage.cl::get(Uln::baseStorage).BaseStorage::getOwner(); + int expectedTreasuryFeeBps = $UlnStorage.cl::get(Uln::treasuryFeeBps); + + (int owner, int treasuryFeeBps) = Uln::getQuoteWorkersInformation($UlnStorage); + + return test::shouldBeTrue( + (owner == expectedOwner) & (treasuryFeeBps == expectedTreasuryFeeBps) + + ); +} + +(int, slice) Serde::Uln::setWorkerFeelibInfos(cell $unused) impure { + cell $UlnStorage = Uln::New( + ULN_MANAGER_ADDRESS, + SRC_EID, + DST_EID + ); + + cell $workerFeelibInfos = _getRandomCode(57); + + cell $expectedUlnStorage = $UlnStorage.cl::set(Uln::workerFeelibInfos, $workerFeelibInfos); + cell $newUlnStorage = Uln::setWorkerFeelibInfos($UlnStorage, $workerFeelibInfos); + + return test::set::equal($newUlnStorage, $expectedUlnStorage); +} + +(int, slice) Serde::Uln::updateWorkerFeelibInfos(cell $unused) impure { + cell $UlnStorage = Uln::New( + ULN_MANAGER_ADDRESS, + SRC_EID, + DST_EID + ); + + cell $workerFeeLibInfos = $UlnStorage + .cl::get(Uln::workerFeelibInfos) + .cl::dict256::setRef(1, _getRandomCode(58)); + + cell $expectedUlnStorage = $UlnStorage.cl::set(Uln::workerFeelibInfos, $workerFeeLibInfos); + + cell $newUlnStorage = Uln::updateWorkerFeelibInfos($UlnStorage, 1, _getRandomCode(58)); + + return test::set::equal($newUlnStorage, $expectedUlnStorage); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::Uln::getDefaultUlnReceiveConfig, "Serde::Uln::getDefaultUlnReceiveConfig"]) + .tpush([Serde::Uln::getConnectionCode, "Serde::Uln::getConnectionCode"]) + .tpush([Serde::Uln::getWorkerFeelibInfos, "Serde::Uln::getWorkerFeelibInfos"]) + .tpush([Serde::Uln::getDefaultUlnSendConfig, "Serde::Uln::getDefaultUlnSendConfig"]) + .tpush([Serde::Uln::getQuoteWorkersInformation, "Serde::Uln::getQuoteWorkersInformation"]) + .tpush([Serde::Uln::setWorkerFeelibInfos, "Serde::Uln::setWorkerFeelibInfos"]) + .tpush([Serde::Uln::updateWorkerFeelibInfos, "Serde::Uln::updateWorkerFeelibInfos"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/testutil.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/testutil.fc new file mode 100644 index 00000000..90a0dc33 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/testutil.fc @@ -0,0 +1,354 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/mocks.fc"; + +() _registerMockWorkers(tuple addresses, int friendAddress) impure { + ;; Register each DVN worker + int len = addresses.tlen(); + repeat(len) { + int dvnAddress = addresses~tpop(); + + ;; Register the DVN worker + updateWorkerFeelib( + MOCK_ULN_WORKER_FEELIB_INFO_WITH_FRIEND(dvnAddress, friendAddress, Uln::CONST::INITIAL_RENT_NANOS) + ); + } +} + +() _registerMockWorkersWithDvnFeeLib(tuple addresses, int friendAddress) impure { + ;; Register each DVN worker + int len = addresses.tlen(); + repeat(len) { + int dvnAddress = addresses~tpop(); + + ;; Register the DVN worker + updateWorkerFeelib( + MOCK_ULN_DVN_FEELIB_INFO_WITH_FRIEND(dvnAddress, friendAddress, Uln::CONST::INITIAL_RENT_NANOS) + ); + } +} + +() _registerMockWorkersWithExecutorFeeLib(tuple addresses, int friendAddress) impure { + ;; Register each DVN worker + int len = addresses.tlen(); + repeat(len) { + int dvnAddress = addresses~tpop(); + + ;; Register the DVN worker + updateWorkerFeelib( + MOCK_ULN_EXECUTOR_FEELIB_INFO_WITH_FRIEND(dvnAddress, friendAddress, Uln::CONST::INITIAL_RENT_NANOS) + ); + } +} + +;; Register: +;; - Default price feed +;; - Default required DVN workers +;; - Default optional DVN workers +;; - Default executor +() _registerDefaultUlnWorkers() impure { + updateWorkerFeelib( + MOCK_PRICE_FEED_WORKER_INFO( + MOCK_DEFAULT_PRICE_FEED_STORAGE(), + DEFAULT_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); + + _registerMockWorkers( + MOCK_SEND_DVN_LIST( + 2, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ), + DEFAULT_PRICE_FEED_ADDRESS + ); + + _registerMockWorkers( + MOCK_SEND_DVN_LIST( + 2, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0) + ), + DEFAULT_PRICE_FEED_ADDRESS + ); + + updateWorkerFeelib( + MOCK_ULN_WORKER_FEELIB_INFO_WITH_FRIEND( + DEFAULT_EXECUTOR, + DEFAULT_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); +} + +;; Register: +;; - Custom price feed +;; - Custom required DVN workers +;; - Custom optional DVN workers +;; - Custom executor +() _registerCustomUlnWorkers() impure { + updateWorkerFeelib( + MOCK_PRICE_FEED_WORKER_INFO( + MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); + + _registerMockWorkers( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + _registerMockWorkers( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + updateWorkerFeelib( + MOCK_ULN_EXECUTOR_FEELIB_INFO_WITH_FRIEND( + CUSTOM_EXECUTOR, + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); +} + +() _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib() impure { + ;; register price feed + updateWorkerFeelib( + MOCK_PRICE_FEED_WORKER_INFO( + MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); + + ;; register required DVN feelibs + _registerMockWorkersWithExecutorFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register optional DVN feelibs + _registerMockWorkersWithExecutorFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register executor feelib + updateWorkerFeelib( + MOCK_ULN_EXECUTOR_FEELIB_INFO_WITH_FRIEND( + CUSTOM_EXECUTOR, + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); +} + +() _registerCustomUlnWorkersAllUsingArbExecutorFeeLib() impure { + ;; register price feed + updateWorkerFeelib( + MOCK_PRICE_FEED_WORKER_INFO( + MOCK_CUSTOM_PRICE_FEED_STORAGE_ARB(), + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); + + ;; register required DVN feelibs + _registerMockWorkersWithExecutorFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register optional DVN feelibs + _registerMockWorkersWithExecutorFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register executor feelib + updateWorkerFeelib( + MOCK_ULN_EXECUTOR_FEELIB_INFO_WITH_FRIEND( + CUSTOM_EXECUTOR, + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); +} + +() _registerCustomUlnWorkersAllUsingOpExecutorFeeLib() impure { + ;; register price feed + updateWorkerFeelib( + MOCK_PRICE_FEED_WORKER_INFO( + MOCK_CUSTOM_PRICE_FEED_STORAGE_OP(), + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); + + ;; register required DVN feelibs + _registerMockWorkersWithExecutorFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register optional DVN feelibs + _registerMockWorkersWithExecutorFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register executor feelib + updateWorkerFeelib( + MOCK_ULN_EXECUTOR_FEELIB_INFO_WITH_FRIEND( + CUSTOM_EXECUTOR, + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); +} + +() _registerCustomUlnWorkersAllUsingDefaultDvnFeeLib() impure { + ;; register price feed + updateWorkerFeelib( + MOCK_PRICE_FEED_WORKER_INFO( + MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); + + ;; register required DVN feelibs + _registerMockWorkersWithDvnFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register optional DVN feelibs + _registerMockWorkersWithDvnFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register "executor feelib" + updateWorkerFeelib( + MOCK_ULN_DVN_FEELIB_INFO_WITH_FRIEND( + CUSTOM_EXECUTOR, + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); +} + +() _registerCustomUlnWorkersAllUsingArbDvnFeeLib() impure { + ;; register price feed + updateWorkerFeelib( + MOCK_PRICE_FEED_WORKER_INFO( + MOCK_CUSTOM_PRICE_FEED_STORAGE_ARB(), + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); + + ;; register required DVN feelibs + _registerMockWorkersWithDvnFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register optional DVN feelibs + _registerMockWorkersWithDvnFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register "executor feelib" + updateWorkerFeelib( + MOCK_ULN_DVN_FEELIB_INFO_WITH_FRIEND( + CUSTOM_EXECUTOR, + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); +} + +() _registerCustomUlnWorkersAllUsingOpDvnFeeLib() impure { + ;; register price feed + updateWorkerFeelib( + MOCK_PRICE_FEED_WORKER_INFO( + MOCK_CUSTOM_PRICE_FEED_STORAGE_OP(), + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); + + ;; register required DVN feelibs + _registerMockWorkersWithDvnFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register optional DVN feelibs + _registerMockWorkersWithDvnFeeLib( + MOCK_SEND_DVN_LIST( + 2, + MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0) + ), + CUSTOM_PRICE_FEED_ADDRESS + ); + + ;; register "executor feelib" + updateWorkerFeelib( + MOCK_ULN_DVN_FEELIB_INFO_WITH_FRIEND( + CUSTOM_EXECUTOR, + CUSTOM_PRICE_FEED_ADDRESS, + Uln::CONST::INITIAL_RENT_NANOS + ) + ); +} + +() _registerDefaultAndCustomUlnWorkers() impure { + _registerDefaultUlnWorkers(); + _registerCustomUlnWorkers(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSend.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSend.fc new file mode 100644 index 00000000..2034fd86 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSend.fc @@ -0,0 +1,325 @@ +#include "testutil.fc"; +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../msgdata/UlnSend.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/utils.fc"; + +#include "../../../../channel/interface.fc"; +#include "../../../../../funC++/actions/call.fc"; + +#include "./mockWorker.fc"; +#include "../../../BytesEncoder.fc"; +#include "../../msgdata/DvnFeesPaidEvent.fc"; +#include "../../msgdata/UlnEvents.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../core/abstract/protocolHandler.fc"; + +slice _testName() { return "ulnSend"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) ulnSend::success::basic(cell $storage) impure { + _registerCustomUlnWorkers(); + + int numWorkers = 5; + + cell mockUlnWorkerEvent = MOCK_ULN_WORKER_EVENT(); + cell expectedUlnWorkerFeelibEvents = UlnWorkerFeelibEvents::FromBuilder( + UlnWorkerFeelibEventsBuilder::create() + .UlnWorkerFeelibEventsBuilder::push(MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), mockUlnWorkerEvent) + .UlnWorkerFeelibEventsBuilder::push(MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), mockUlnWorkerEvent) + .UlnWorkerFeelibEventsBuilder::push(MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), MOCK_ULN_WORKER_EVENT()) + .UlnWorkerFeelibEventsBuilder::push(MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), MOCK_ULN_WORKER_EVENT()) + .UlnWorkerFeelibEventsBuilder::push(CUSTOM_EXECUTOR, mockUlnWorkerEvent) + ); + + cell dvnPaidEvent = DvnFeesPaidEvent::New( + MOCK_CUSTOM_REQUIRED_DVN_CELL(2, 0), + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + serializePayees( + unsafeTuple([ + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), NATIVE_FEE], + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), NATIVE_FEE] + ]) + ) + ); + + cell executorPaidEvent = ExecutorFeePaidEvent::New(CUSTOM_EXECUTOR, NATIVE_FEE); + + cell $expectedUlnEvents = UlnEvents::New( + expectedUlnWorkerFeelibEvents, + dvnPaidEvent, + executorPaidEvent + ); + + int treasuryFee = ((NATIVE_FEE * numWorkers) * TREASURY_FEE_BPS) / 10000; + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + (NATIVE_FEE * numWorkers) + treasuryFee, + 0, + MOCK_LZ_SEND(), + BytesEncoder::build(MOCK_NONCELESS_PACKET()).BytesEncoder::serialize(), + serializePayees( + unsafeTuple([ + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), NATIVE_FEE], + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), NATIVE_FEE], + [CUSTOM_EXECUTOR, NATIVE_FEE], + [getOwner(), treasuryFee] + ]) + ), + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + $expectedUlnEvents, + Channel::NO_ERROR + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; both default and custom configs have executorNull set to true (see UlnSendConfig::newWithDefaults) +(int, slice) ulnSend::success::bothConfigsExecutorNull::workerQuoteFailed(cell $storage) impure { + cell $customSendConfig = MOCK_CUSTOM_ULN_SEND_CONFIG() + .cl::set(UlnSendConfig::executorNull, true); + + cell $ulnSend = MOCK_ULN_SEND() + .cl::set(md::UlnSend::customUlnSendConfig, $customSendConfig); + + return test::handler::shouldPass( + ulnSend, + $ulnSend, + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND(MOCK_LZ_SEND()) + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; both default and custom configs have requiredDVNsNull set to true (see UlnSendConfig::newWithDefaults) +(int, slice) ulnSend::success::bothConfigsRequiredDVNsNull::workerQuoteFailed(cell $storage) impure { + cell $customSendConfig = MOCK_CUSTOM_ULN_SEND_CONFIG() + .cl::set(UlnSendConfig::requiredDVNsNull, true); + + cell $ulnSend = MOCK_ULN_SEND() + .cl::set(md::UlnSend::customUlnSendConfig, $customSendConfig); + + return test::handler::shouldPass( + ulnSend, + $ulnSend, + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND(MOCK_LZ_SEND()) + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; both default and custom configs have optionalDVNsNull set to true (see UlnSendConfig::newWithDefaults) +(int, slice) ulnSend::success::bothConfigsOptionalDVNsNull::workerQuoteFailed(cell $storage) impure { + cell $customSendConfig = MOCK_CUSTOM_ULN_SEND_CONFIG() + .cl::set(UlnSendConfig::optionalDVNsNull, true); + + cell $ulnSend = MOCK_ULN_SEND() + .cl::set(md::UlnSend::customUlnSendConfig, $customSendConfig); + + return test::handler::shouldPass( + ulnSend, + $ulnSend, + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND(MOCK_LZ_SEND()) + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; both default and custom configs have optionalDVNsNull set to true (see UlnSendConfig::newWithDefaults) +(int, slice) ulnSend::success::bothConfigsConfirmationsNull::workerQuoteFailed(cell $storage) impure { + cell $customSendConfig = MOCK_CUSTOM_ULN_SEND_CONFIG() + .cl::set(UlnSendConfig::confirmationsNull, true); + + cell $ulnSend = MOCK_ULN_SEND() + .cl::set(md::UlnSend::customUlnSendConfig, $customSendConfig); + + return test::handler::shouldPass( + ulnSend, + $ulnSend, + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND(MOCK_LZ_SEND()) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnSend::success::customExecutorNotInWorkerInfos::workerQuoteFailed(cell $storage) impure { + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND(MOCK_LZ_SEND()) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnSend::success::requiredCustomDvnNotInWorkerInfos::workerQuoteFailed(cell $storage) impure { + _registerMockWorkers( + MOCK_SEND_DVN_LIST(1, CUSTOM_EXECUTOR), + NULLADDRESS + ); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND(MOCK_LZ_SEND()) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnSend::success::optionalCustomDvnNotInWorkerInfos::workerQuoteFailed(cell $storage) impure { + _registerMockWorkers( + MOCK_SEND_DVN_LIST(2, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + NULLADDRESS + ); + _registerMockWorkers( + MOCK_SEND_DVN_LIST(1, CUSTOM_EXECUTOR), + NULLADDRESS + ); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND(MOCK_LZ_SEND()) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnSend::success::greaterThanMaxMsgBytes::workerQuoteFailed(cell $storage) impure { + _registerCustomUlnWorkers(); + + return test::handler::shouldPass( + ulnSend, + md::UlnSend::New( + MOCK_LZ_SEND_WITH_MAX_PACKET(), + MOCK_CUSTOM_ULN_SEND_CONFIG(), + cl::nullObject(), + CHANNEL_ADDRESS + ), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND(MOCK_LZ_SEND_WITH_MAX_PACKET()) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([ulnSend::success::basic, "ulnSend::success::basic"]) + .tpush([ulnSend::success::bothConfigsExecutorNull::workerQuoteFailed, "ulnSend::success::bothConfigsExecutorNull::workerQuoteFailed"]) + .tpush([ulnSend::success::bothConfigsRequiredDVNsNull::workerQuoteFailed, "ulnSend::success::bothConfigsRequiredDVNsNull::workerQuoteFailed"]) + .tpush([ulnSend::success::bothConfigsOptionalDVNsNull::workerQuoteFailed, "ulnSend::success::bothConfigsOptionalDVNsNull::workerQuoteFailed"]) + .tpush([ulnSend::success::bothConfigsConfirmationsNull::workerQuoteFailed, "ulnSend::success::bothConfigsConfirmationsNull::workerQuoteFailed"]) + .tpush([ulnSend::success::customExecutorNotInWorkerInfos::workerQuoteFailed, "ulnSend::success::customExecutorNotInWorkerInfos::workerQuoteFailed"]) + .tpush([ulnSend::success::requiredCustomDvnNotInWorkerInfos::workerQuoteFailed, "ulnSend::success::requiredCustomDvnNotInWorkerInfos::workerQuoteFailed"]) + .tpush([ulnSend::success::optionalCustomDvnNotInWorkerInfos::workerQuoteFailed, "ulnSend::success::optionalCustomDvnNotInWorkerInfos::workerQuoteFailed"]) + .tpush([ulnSend::success::greaterThanMaxMsgBytes::workerQuoteFailed, "ulnSend::success::greaterThanMaxMsgBytes::workerQuoteFailed"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbDvnFeeLib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbDvnFeeLib.fc new file mode 100644 index 00000000..043a27c1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbDvnFeeLib.fc @@ -0,0 +1,111 @@ +#include "testutil.fc"; +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../msgdata/UlnSend.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/utils.fc"; + +#include "../../../../channel/interface.fc"; +#include "../../../../../funC++/actions/call.fc"; + +#include "../../workerFeeLibs/dvnFeeLib/handler.fc"; + +#include "../../workerFeeLibs/priceFeedFeeLib/arbitrum/handler.fc"; + +slice _testName() { return "ulnSend"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) ulnSendWithDvnFeeLib::success::basic(cell $storage) impure { + _registerCustomUlnWorkersAllUsingArbDvnFeeLib(); + + tuple dvnPayees = unsafeTuple([ + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), CUSTOM_DVN_ARB_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), CUSTOM_DVN_ARB_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), CUSTOM_DVN_ARB_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), CUSTOM_DVN_ARB_FEELIB_NATIVE_FEE] + ]); + + cell dvnPaidEvent = DvnFeesPaidEvent::New( + MOCK_CUSTOM_REQUIRED_DVN_CELL(2, 0), + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + serializePayees(dvnPayees) + ); + + cell executorPaidEvent = ExecutorFeePaidEvent::New(CUSTOM_EXECUTOR, CUSTOM_DVN_ARB_FEELIB_NATIVE_FEE); + + int totalWorkerFee = (CUSTOM_DVN_ARB_FEELIB_NATIVE_FEE * 5); + int treasuryFee = totalWorkerFee * TREASURY_FEE_BPS / 10000; + + tuple allPayees = dvnPayees + .tpush([CUSTOM_EXECUTOR, CUSTOM_DVN_ARB_FEELIB_NATIVE_FEE]) + .tpush([getOwner(), treasuryFee]); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 5 * CUSTOM_DVN_ARB_FEELIB_NATIVE_FEE + treasuryFee, + 0, + MOCK_LZ_SEND(), + BytesEncoder::build(MOCK_NONCELESS_PACKET()).BytesEncoder::serialize(), + serializePayees(allPayees), + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + UlnEvents::New( + cl::nullObject(), + dvnPaidEvent, + executorPaidEvent + ), + Channel::NO_ERROR + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + .tpush([ulnSendWithDvnFeeLib::success::basic, "ulnSendWithDvnFeeLib::success::basic"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbExecFeeLib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbExecFeeLib.fc new file mode 100644 index 00000000..c1c619cb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbExecFeeLib.fc @@ -0,0 +1,157 @@ +#include "testutil.fc"; +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../msgdata/UlnSend.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/utils.fc"; + +#include "../../../../channel/interface.fc"; +#include "../../../../../funC++/actions/call.fc"; + +#include "../../workerFeeLibs/executorFeeLib/handler.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../classes/lz/Packet.fc"; +#include "../../../../../classes/msgdata/LzSend.fc"; + +#include "../../workerFeeLibs/priceFeedFeeLib/arbitrum/handler.fc"; + +slice _testName() { return "ulnSend"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) ulnSendWithExecFeeLib::success::basic(cell $storage) impure { + _registerCustomUlnWorkersAllUsingArbExecutorFeeLib(); + + tuple dvnPayees = unsafeTuple([ + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), CUSTOM_EXECUTOR_ARB_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), CUSTOM_EXECUTOR_ARB_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), CUSTOM_EXECUTOR_ARB_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), CUSTOM_EXECUTOR_ARB_FEELIB_NATIVE_FEE] + ]); + + cell dvnPaidEvent = DvnFeesPaidEvent::New( + MOCK_CUSTOM_REQUIRED_DVN_CELL(2, 0), + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + serializePayees(dvnPayees) + ); + + cell executorPaidEvent = ExecutorFeePaidEvent::New(CUSTOM_EXECUTOR, CUSTOM_EXECUTOR_ARB_FEELIB_NATIVE_FEE); + + int totalWorkerFee = (CUSTOM_EXECUTOR_ARB_FEELIB_NATIVE_FEE * 5); + int treasuryFee = totalWorkerFee * TREASURY_FEE_BPS / 10000; + + tuple allPayees = dvnPayees + .tpush([CUSTOM_EXECUTOR, CUSTOM_EXECUTOR_ARB_FEELIB_NATIVE_FEE]) + .tpush([getOwner(), treasuryFee]); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 5 * CUSTOM_EXECUTOR_ARB_FEELIB_NATIVE_FEE + treasuryFee, + 0, + MOCK_LZ_SEND(), + BytesEncoder::build(MOCK_NONCELESS_PACKET()).BytesEncoder::serialize(), + serializePayees(allPayees), + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + UlnEvents::New( + cl::nullObject(), + dvnPaidEvent, + executorPaidEvent + ), + Channel::NO_ERROR + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnSendWithExecFeeLib::revert::noLzReceiveValueToV1(cell $storage) impure { + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + cell $mockV1Packet = MOCK_NONCELESS_PACKET() + .cl::set(lz::Packet::path, MOCK_V1_SEND_PATH()); + + cell $mockV1LzSend = md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + $mockV1Packet, + MOCK_CALLBACK_DATA() + ) + .cl::set(md::LzSend::sendRequestId, 0) + .cl::set(md::LzSend::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell $ulnSend = md::UlnSend::New( + $mockV1LzSend, + MOCK_CUSTOM_ULN_SEND_CONFIG(), + UlnConnection::New(ULN_MANAGER_ADDRESS, MOCK_SEND_PATH(), ULN_ADDRESS), + CHANNEL_ADDRESS + ); + + return test::handler::shouldPass( + ulnSend, + $ulnSend, + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + _failedMsglibSendCallback($mockV1LzSend) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([ulnSendWithExecFeeLib::success::basic, "ulnSendWithExecFeeLib::success::basic"]) + .tpush([ulnSendWithExecFeeLib::revert::noLzReceiveValueToV1, "ulnSendWithExecFeeLib::revert::noLzReceiveValueToV1"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultDvnFeeLib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultDvnFeeLib.fc new file mode 100644 index 00000000..6b99257d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultDvnFeeLib.fc @@ -0,0 +1,111 @@ +#include "testutil.fc"; +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../msgdata/UlnSend.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/utils.fc"; + +#include "../../../../channel/interface.fc"; +#include "../../../../../funC++/actions/call.fc"; + +#include "../../workerFeeLibs/dvnFeeLib/handler.fc"; + +#include "../../workerFeeLibs/priceFeedFeeLib/default/handler.fc"; + +slice _testName() { return "ulnSend"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) ulnSendWithDvnFeeLib::success::basic(cell $storage) impure { + _registerCustomUlnWorkersAllUsingDefaultDvnFeeLib(); + + tuple dvnPayees = unsafeTuple([ + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE] + ]); + + cell dvnPaidEvent = DvnFeesPaidEvent::New( + MOCK_CUSTOM_REQUIRED_DVN_CELL(2, 0), + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + serializePayees(dvnPayees) + ); + + cell executorPaidEvent = ExecutorFeePaidEvent::New(CUSTOM_EXECUTOR, CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE); + + int totalWorkerFee = (CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE * 5); + int treasuryFee = totalWorkerFee * TREASURY_FEE_BPS / 10000; + + tuple allPayees = dvnPayees + .tpush([CUSTOM_EXECUTOR, CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE]) + .tpush([getOwner(), treasuryFee]); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 5 * CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE + treasuryFee, + 0, + MOCK_LZ_SEND(), + BytesEncoder::build(MOCK_NONCELESS_PACKET()).BytesEncoder::serialize(), + serializePayees(allPayees), + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + UlnEvents::New( + cl::nullObject(), + dvnPaidEvent, + executorPaidEvent + ), + Channel::NO_ERROR + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + .tpush([ulnSendWithDvnFeeLib::success::basic, "ulnSendWithDvnFeeLib::success::basic"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultExecFeeLib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultExecFeeLib.fc new file mode 100644 index 00000000..b55f2ea0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultExecFeeLib.fc @@ -0,0 +1,198 @@ +#include "testutil.fc"; +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../msgdata/UlnSend.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/utils.fc"; + +#include "../../../../channel/interface.fc"; +#include "../../../../../funC++/actions/call.fc"; + +#include "../../workerFeeLibs/executorFeeLib/handler.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../classes/lz/Packet.fc"; +#include "../../../../../classes/msgdata/LzSend.fc"; + +#include "../../workerFeeLibs/priceFeedFeeLib/default/handler.fc"; + +slice _testName() { return "ulnSend"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) ulnSendWithExecFeeLib::success::basic(cell $storage) impure { + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + tuple dvnPayees = unsafeTuple([ + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), CUSTOM_EXECUTOR_DEFAULT_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), CUSTOM_EXECUTOR_DEFAULT_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), CUSTOM_EXECUTOR_DEFAULT_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), CUSTOM_EXECUTOR_DEFAULT_FEELIB_NATIVE_FEE] + ]); + + cell dvnPaidEvent = DvnFeesPaidEvent::New( + MOCK_CUSTOM_REQUIRED_DVN_CELL(2, 0), + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + serializePayees(dvnPayees) + ); + + cell executorPaidEvent = ExecutorFeePaidEvent::New(CUSTOM_EXECUTOR, CUSTOM_EXECUTOR_DEFAULT_FEELIB_NATIVE_FEE); + + int totalWorkerFee = (CUSTOM_EXECUTOR_DEFAULT_FEELIB_NATIVE_FEE * 5); + int treasuryFee = totalWorkerFee * TREASURY_FEE_BPS / 10000; + + tuple allPayees = dvnPayees + .tpush([CUSTOM_EXECUTOR, CUSTOM_EXECUTOR_DEFAULT_FEELIB_NATIVE_FEE]) + .tpush([getOwner(), treasuryFee]); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 5 * CUSTOM_EXECUTOR_DEFAULT_FEELIB_NATIVE_FEE + treasuryFee, + 0, + MOCK_LZ_SEND(), + BytesEncoder::build(MOCK_NONCELESS_PACKET()).BytesEncoder::serialize(), + serializePayees(allPayees), + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + UlnEvents::New( + cl::nullObject(), + dvnPaidEvent, + executorPaidEvent + ), + Channel::NO_ERROR + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnSendWithExecFeeLib::revert::noLzReceiveValueToV1(cell $storage) impure { + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + cell $mockV1Packet = MOCK_NONCELESS_PACKET() + .cl::set(lz::Packet::path, MOCK_V1_SEND_PATH()); + + cell $mockV1LzSend = md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + $mockV1Packet, + MOCK_CALLBACK_DATA() + ) + .cl::set(md::LzSend::sendRequestId, 0) + .cl::set(md::LzSend::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell $ulnSend = md::UlnSend::New( + $mockV1LzSend, + MOCK_CUSTOM_ULN_SEND_CONFIG(), + UlnConnection::New(ULN_MANAGER_ADDRESS, MOCK_SEND_PATH(), ULN_ADDRESS), + CHANNEL_ADDRESS + ); + + return test::handler::shouldPass( + ulnSend, + $ulnSend, + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + _failedMsglibSendCallback($mockV1LzSend) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnSendWithExecFeeLib::revert::valueOverCap(cell $storage) impure { + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + cell $mockV1LzSend = md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1().cl::set(md::OptionsV1::nativeDropAmount, EXECUTOR_FEELIB_NATIVE_CAP + 1), + MOCK_ENFORCED_OPTIONS_V1(), + MOCK_NONCELESS_PACKET(), + MOCK_CALLBACK_DATA() + ) + .cl::set(md::LzSend::sendRequestId, 0) + .cl::set(md::LzSend::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell $ulnSend = md::UlnSend::New( + $mockV1LzSend, + MOCK_CUSTOM_ULN_SEND_CONFIG(), + UlnConnection::New(ULN_MANAGER_ADDRESS, MOCK_SEND_PATH(), ULN_ADDRESS), + CHANNEL_ADDRESS + ); + + return test::handler::shouldPass( + ulnSend, + $ulnSend, + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + _failedMsglibSendCallback($mockV1LzSend) + ) + ]), + getContractStorage(), + txnContext + ); +} + + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([ulnSendWithExecFeeLib::success::basic, "ulnSendWithExecFeeLib::success::basic"]) + .tpush([ulnSendWithExecFeeLib::revert::noLzReceiveValueToV1, "ulnSendWithExecFeeLib::revert::noLzReceiveValueToV1"]) + .tpush([ulnSendWithExecFeeLib::revert::valueOverCap, "ulnSendWithExecFeeLib::revert::valueOverCap"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpDvnFeeLib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpDvnFeeLib.fc new file mode 100644 index 00000000..d07dc0cd --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpDvnFeeLib.fc @@ -0,0 +1,111 @@ +#include "testutil.fc"; +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../msgdata/UlnSend.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/utils.fc"; + +#include "../../../../channel/interface.fc"; +#include "../../../../../funC++/actions/call.fc"; + +#include "../../workerFeeLibs/dvnFeeLib/handler.fc"; + +#include "../../workerFeeLibs/priceFeedFeeLib/optimism/handler.fc"; + +slice _testName() { return "ulnSend"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) ulnSendWithDvnFeeLib::success::basic(cell $storage) impure { + _registerCustomUlnWorkersAllUsingOpDvnFeeLib(); + + tuple dvnPayees = unsafeTuple([ + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), CUSTOM_DVN_OP_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), CUSTOM_DVN_OP_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), CUSTOM_DVN_OP_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), CUSTOM_DVN_OP_FEELIB_NATIVE_FEE] + ]); + + cell dvnPaidEvent = DvnFeesPaidEvent::New( + MOCK_CUSTOM_REQUIRED_DVN_CELL(2, 0), + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + serializePayees(dvnPayees) + ); + + cell executorPaidEvent = ExecutorFeePaidEvent::New(CUSTOM_EXECUTOR, CUSTOM_DVN_OP_FEELIB_NATIVE_FEE); + + int totalWorkerFee = (CUSTOM_DVN_OP_FEELIB_NATIVE_FEE * 5); + int treasuryFee = totalWorkerFee * TREASURY_FEE_BPS / 10000; + + tuple allPayees = dvnPayees + .tpush([CUSTOM_EXECUTOR, CUSTOM_DVN_OP_FEELIB_NATIVE_FEE]) + .tpush([getOwner(), treasuryFee]); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 5 * CUSTOM_DVN_OP_FEELIB_NATIVE_FEE + treasuryFee, + 0, + MOCK_LZ_SEND(), + BytesEncoder::build(MOCK_NONCELESS_PACKET()).BytesEncoder::serialize(), + serializePayees(allPayees), + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + UlnEvents::New( + cl::nullObject(), + dvnPaidEvent, + executorPaidEvent + ), + Channel::NO_ERROR + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + .tpush([ulnSendWithDvnFeeLib::success::basic, "ulnSendWithDvnFeeLib::success::basic"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpExecFeeLib.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpExecFeeLib.fc new file mode 100644 index 00000000..5f33e6c1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpExecFeeLib.fc @@ -0,0 +1,157 @@ +#include "testutil.fc"; +#include "../handler.fc"; +#include "../interface.fc"; +#include "../../msgdata/UlnSend.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnWorkerFeelibEvents.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/utils.fc"; + +#include "../../../../channel/interface.fc"; +#include "../../../../../funC++/actions/call.fc"; + +#include "../../workerFeeLibs/executorFeeLib/handler.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../classes/lz/Packet.fc"; +#include "../../../../../classes/msgdata/LzSend.fc"; + +#include "../../workerFeeLibs/priceFeedFeeLib/optimism/handler.fc"; + +slice _testName() { return "ulnSend"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ) + ); +} + +cell createContractStorage() impure { + setContractStorage(Uln::New(ULN_MANAGER_ADDRESS, SRC_EID, DST_EID)); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) ulnSendWithExecFeeLib::success::basic(cell $storage) impure { + _registerCustomUlnWorkersAllUsingOpExecutorFeeLib(); + + tuple dvnPayees = unsafeTuple([ + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(1), CUSTOM_EXECUTOR_OP_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), CUSTOM_EXECUTOR_OP_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(1), CUSTOM_EXECUTOR_OP_FEELIB_NATIVE_FEE], + [MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0), CUSTOM_EXECUTOR_OP_FEELIB_NATIVE_FEE] + ]); + + cell dvnPaidEvent = DvnFeesPaidEvent::New( + MOCK_CUSTOM_REQUIRED_DVN_CELL(2, 0), + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + serializePayees(dvnPayees) + ); + + cell executorPaidEvent = ExecutorFeePaidEvent::New(CUSTOM_EXECUTOR, CUSTOM_EXECUTOR_OP_FEELIB_NATIVE_FEE); + + int totalWorkerFee = (CUSTOM_EXECUTOR_OP_FEELIB_NATIVE_FEE * 5); + int treasuryFee = totalWorkerFee * TREASURY_FEE_BPS / 10000; + + tuple allPayees = dvnPayees + .tpush([CUSTOM_EXECUTOR, CUSTOM_EXECUTOR_OP_FEELIB_NATIVE_FEE]) + .tpush([getOwner(), treasuryFee]); + + return test::handler::shouldPass( + ulnSend, + MOCK_ULN_SEND(), + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + md::MsglibSendCallback::New( + 5 * CUSTOM_EXECUTOR_OP_FEELIB_NATIVE_FEE + treasuryFee, + 0, + MOCK_LZ_SEND(), + BytesEncoder::build(MOCK_NONCELESS_PACKET()).BytesEncoder::serialize(), + serializePayees(allPayees), + PacketV1::nonceOffsetBytes, + PacketV1::nonceBytes, + PacketV1::guidOffsetBytes, + PacketV1::guidBytes, + UlnEvents::New( + cl::nullObject(), + dvnPaidEvent, + executorPaidEvent + ), + Channel::NO_ERROR + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnSendWithExecFeeLib::revert::noLzReceiveValueToV1(cell $storage) impure { + _registerCustomUlnWorkersAllUsingDefaultExecutorFeeLib(); + + cell $mockV1Packet = MOCK_NONCELESS_PACKET() + .cl::set(lz::Packet::path, MOCK_V1_SEND_PATH()); + + cell $mockV1LzSend = md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + $mockV1Packet, + MOCK_CALLBACK_DATA() + ) + .cl::set(md::LzSend::sendRequestId, 0) + .cl::set(md::LzSend::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); + + cell $ulnSend = md::UlnSend::New( + $mockV1LzSend, + MOCK_CUSTOM_ULN_SEND_CONFIG(), + UlnConnection::New(ULN_MANAGER_ADDRESS, MOCK_SEND_PATH(), ULN_ADDRESS), + CHANNEL_ADDRESS + ); + + return test::handler::shouldPass( + ulnSend, + $ulnSend, + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::MSGLIB_SEND_CALLBACK, + _failedMsglibSendCallback($mockV1LzSend) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([ulnSendWithExecFeeLib::success::basic, "ulnSendWithExecFeeLib::success::basic"]) + .tpush([ulnSendWithExecFeeLib::revert::noLzReceiveValueToV1, "ulnSendWithExecFeeLib::revert::noLzReceiveValueToV1"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/util.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/util.fc new file mode 100644 index 00000000..5691d18c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/uln/tests/util.fc @@ -0,0 +1,44 @@ +#include "../handler.fc"; +#include "../interface.fc"; + +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/stdlib.fc"; + +slice _testName() { return "ulnUtils"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +const int True = 1; + +;; this test doesn't actually mean anything +(int, slice) helper::success::globvar3Unused(cell $args) impure { + return test::shouldBeTrue(_globvarIsNull(3)); +} + +(int, slice) UlnWorkerFeelibInfo::sizeInvariant(cell $args) impure { + cell $info = UlnWorkerFeelibInfo::New( + 1, + begin_cell().store_uint256(1).end_cell(), + begin_cell().store_uint256(2).end_cell(), + 2, + 3, + 4, + true + ); + (int numCells, _, _) = compute_data_size($info, MAX_U8); + return test::shouldBeTrue(numCells == 4); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([helper::success::globvar3Unused, "helper::success::globvar3Unused"]) + .tpush([UlnWorkerFeelibInfo::sizeInvariant, "UlnWorkerFeelibInfo::sizeInvariant"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/handler.fc new file mode 100644 index 00000000..09e60db0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/handler.fc @@ -0,0 +1,639 @@ +#include "../../../core/abstract/protocolHandler.fc"; + +#include "../callbackOpcodes.fc"; + +#include "../msgdata/InitUlnConnection.fc"; +#include "../msgdata/UlnSend.fc"; +#include "../msgdata/UlnReceiveConfig.fc"; +#include "../msgdata/UlnSendConfig.fc"; +#include "../msgdata/UlnVerification.fc"; +#include "../msgdata/VerificationStatus.fc"; +#include "../msgdata/Attestation.fc"; + +#include "../uln/interface.fc"; +#include "../../interface.fc"; + +#include "../../../interfaces.fc"; + +#include "../../../../protocol/endpoint/interface.fc"; + +#include "../../../../classes/lz/Packet.fc"; +#include "../../../../classes/msgdata/ChannelNonceInfo.fc"; +#include "../../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../classes/msgdata/MdObj.fc"; +#include "../../../../classes/msgdata/Nonce.fc"; +#include "../../../../classes/msgdata/SetAddress.fc"; + +#include "../../../../funC++/dataStructures/AddressList.fc"; +#include "../../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; + +#include "interface.fc"; +#include "storage.fc"; +#include "utils.fc"; + +;;; ================INTERFACE FUNCTIONS===================== +(cell, tuple) _initialize(cell $initUlnConnection) impure inline { + (cell $storage, tuple actions) = preamble(); + + ;; sanitized by the ULN Manager + cell $ulnSendConfigOApp = $initUlnConnection.cl::get( + md::InitUlnConnection::ulnSendConfigOApp + ); + + ;; sanitized by the ULN Manager + cell $UlnReceiveConfigOApp = $initUlnConnection.cl::get( + md::InitUlnConnection::ulnReceiveConfigOApp + ); + + int endpointAddress = $initUlnConnection.cl::get
(md::InitUlnConnection::endpointAddress); + int channelAddress = $initUlnConnection.cl::get
(md::InitUlnConnection::channelAddress); + + return ( + $storage + .cl::set(UlnConnection::UlnSendConfigOApp, $ulnSendConfigOApp) + .cl::set(UlnConnection::UlnReceiveConfigOApp, $UlnReceiveConfigOApp) + .cl::set(UlnConnection::endpointAddress, endpointAddress) + .cl::set(UlnConnection::channelAddress, channelAddress) + .cl::set(UlnConnection::commitPOOO, POOO::New()), + actions + ); +} + +int _getEventSink() impure inline { + return getOwner(); +} + +() assertChannel() impure inline { + throw_unless( + UlnConnection::ERROR::onlyChannel, + getCaller() == getContractStorage().UlnConnection::getChannelAddress() + ); +} + +() assertUln() impure inline { + throw_unless( + UlnConnection::ERROR::onlyUln, + getCaller() == getContractStorage().UlnConnection::getUlnAddress() + ); +} + +int _committable( + cell hashlookups, + int nonce, + int packetHash, + cell requiredDVNs, + cell optionalDVNs, + int optionalDVNThreshold, + int requiredConfirmations +) impure inline { + slice requiredDVNsSlice = requiredDVNs.begin_parse(); + ;; iterate through each of the required DVNs + + int dvnAddress = requiredDVNsSlice~AddressList::next(); + + while (dvnAddress > NULLADDRESS) { + cell $attestation = UlnConnection::utils::getHashLookup( + hashlookups, + nonce, + dvnAddress + ); + if ($attestation.cl::isNullObject()) { + return false; + } + + (int hash, int confirmations) = $attestation.lz::Attestation::deserialize(); + if ( + (confirmations < requiredConfirmations) + | (hash != packetHash) + ) { + return false; + } + dvnAddress = requiredDVNsSlice~AddressList::next(); + } + + int optionalDVNAttestations = 0; + + if (optionalDVNThreshold == 0) { + ;; Short-circuit for the common case of 0 optional DVN + return true; + } + + slice optionalDVNsSlice = optionalDVNs.begin_parse(); + dvnAddress = optionalDVNsSlice~AddressList::next(); + while ((dvnAddress > NULLADDRESS) & (optionalDVNAttestations < optionalDVNThreshold)) { + cell $attestation = UlnConnection::utils::getHashLookup( + hashlookups, + nonce, + dvnAddress + ); + + ;; Nested if, else case is simply to do nothing + ifnot ($attestation.cl::isNullObject()) { + (int hash, int confirmations) = $attestation.lz::Attestation::deserialize(); + if ((confirmations >= requiredConfirmations) & (hash == packetHash)) { + optionalDVNAttestations += 1; + } + } + dvnAddress = optionalDVNsSlice~AddressList::next(); + } + + if ( + (optionalDVNAttestations < optionalDVNThreshold) + ) { + ;; If not enough dvn attestations, return false + return false; + } + return true; +} + +int committableView(int nonce, cell $packet, cell $defaultUlnReceiveConfig) impure method_id { + int firstUnexecutedNonce = getContractStorage().cl::get(UlnConnection::firstUnexecutedNonce); + if (nonce < firstUnexecutedNonce) { + return UlnConnection::verificationStatus::EXECUTED; + } + cell $commitPOOO = getContractStorage().cl::get(UlnConnection::commitPOOO); + int firstUncommittedNonce = $commitPOOO.cl::get(POOO::nextEmpty); + + if (nonce < firstUncommittedNonce) { + return UlnConnection::verificationStatus::COMMITTED; + } elseif (nonce > $commitPOOO.POOO::maxSettableBit()) { + return UlnConnection::verificationStatus::VERIFYING; + } elseif ($commitPOOO.POOO::isBitSet(nonce)) { + return UlnConnection::verificationStatus::COMMITTED; + } + + ( + cell requiredDVNs, + cell optionalDVNs, + int optionalDVNThreshold, + int requiredConfirmations, + _, + int isValid + ) = UlnReceiveConfig::utils::getCommitConfig( + getContractStorage().cl::get(UlnConnection::UlnReceiveConfigOApp), + $defaultUlnReceiveConfig + ); + + ifnot (isValid) { + return UlnConnection::verificationStatus::CONFIGURATION_ERROR; + } + + cell hashLookups = getContractStorage().cl::get(UlnConnection::hashLookups); + + if (_committable( + hashLookups, + nonce, + $packet.cl::hash(), + requiredDVNs, + optionalDVNs, + optionalDVNThreshold, + requiredConfirmations + )) { + return UlnConnection::verificationStatus::COMMITTABLE; + } + return UlnConnection::verificationStatus::VERIFYING; +} + +int verifiedView(int dvnAddress, int nonce, int packetHash, int requiredConfirmations) impure method_id { + cell hashLookups = getContractStorage().cl::get(UlnConnection::hashLookups); + cell $attestation = UlnConnection::utils::getHashLookup( + hashLookups, + nonce, + dvnAddress + ); + + if ($attestation.cl::isNullObject()) { + return false; + } + + int confirmations = $attestation.cl::get(lz::Attestation::confirmations); + int storedHash = $attestation.cl::get(lz::Attestation::hash); + return (confirmations >= requiredConfirmations) & (storedHash == packetHash); +} + +int _dvnIsRequiredOrOptional(slice requiredDVNsSlice, slice optionalDVNsSlice, int dvnAddress) impure inline { + if (AddressList::includes(dvnAddress, requiredDVNsSlice)) { + ;; Most configurations will only use required DVNs, so short-circuit return if it is required + return true; + } + + return AddressList::includes(dvnAddress, optionalDVNsSlice); +} + +int _isDvnConfigured(int dvnAddress, cell $customUlnReceiveConfigOApp, cell $defaultUlnReceiveConfigOApp) impure inline { + (cell requiredDVNs, cell optionalDVNs, int isValid) = UlnReceiveConfig::utils::getVerifyConfig( + $customUlnReceiveConfigOApp, + $defaultUlnReceiveConfigOApp + ); + + if (isValid) { + return _dvnIsRequiredOrOptional( + requiredDVNs.begin_parse(), + optionalDVNs.begin_parse(), + dvnAddress + ); + } + return false; +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + if ( + (op == MsglibConnection::OP::MSGLIB_CONNECTION_SEND) + | (op == MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK) + ) { + return assertChannel(); + } elseif ( + (op == UlnConnection::OP::ULN_CONNECTION_VERIFY) + | (op == UlnConnection::OP::ULN_CONNECTION_COMMIT_PACKET) + ) { + return assertUln(); + } elseif ( + (op == MsglibConnection::OP::MSGLIB_CONNECTION_QUOTE) + ) { + return (); + } elseif (op == UlnConnection::OP::GARBAGE_COLLECT_INVALID_ATTESTATIONS) { + return assertUln(); + } elseif (op == MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE) { + return assertChannel(); + } elseif ( + (op == UlnConnection::OP::SET_OAPP_ULN_SEND_CONFIG) + | (op == UlnConnection::OP::SET_OAPP_ULN_RECEIVE_CONFIG) + ) { + return assertOwner(); + } elseif (op == UlnConnection::OP::GARBAGE_COLLECT_EXECUTED_NONCES) { + return (); + } else { + ;; we must put a check for all opcodes to make sure we don't + ;; mistakenly miss an opp code's permissions + throw(BaseInterface::ERROR::invalidOpcode); + } +} + +;;; ==========================HANDLERS===================================== + +;; permissionless +tuple msglibConnectionQuote(cell $lzSend) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + actions~pushAction( + $storage.cl::get
(UlnConnection::ulnAddress), + Uln::OP::ULN_QUOTE, + md::UlnSend::New( + $lzSend, + $storage.cl::get(UlnConnection::UlnSendConfigOApp), + getInitialStorage(), + getCaller() + ) + ); + + return actions; +} + +;; only uln +tuple msglibConnectionSend(cell $lzSend) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + ( + int ulnAddress, + int channelAddress, + cell $ulnSendConfigOApp + ) = $storage.UlnConnection::getSendInformation(); + + actions~pushAction( + ulnAddress, + Uln::OP::ULN_SEND, + md::UlnSend::build( + $lzSend, + $ulnSendConfigOApp, + getInitialStorage(), + channelAddress + ) + ); + + return actions; +} + +;; permissionless +;; @in $extendedMd { md: Verification, obj: defaultUlnReceiveConfig, address: dvnAddress } +tuple ulnConnectionVerify(cell $extendedMd) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedExtendedMd = $extendedMd.md::ExtendedMd::sanitize(); + + ( + cell $ulnVerification, + cell $defaultUlnReceiveConfigOApp, + int dvnAddress + ) = $sanitizedExtendedMd.md::ExtendedMd::deserialize(); + + ( + int nonce, + cell $attestation + ) = $ulnVerification.md::UlnVerification::deserialize(); + + ( + int firstUnexecutedNonce, + cell hashLookups, + cell $ulnReceiveConfigOApp + ) = $storage.UlnConnection::getVerifyInformation(); + + ;; assume success unless something fails in the verification logic + int statusCode = UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED; + + if ( + (nonce < firstUnexecutedNonce) + | (nonce >= (firstUnexecutedNonce + UlnConnection::MAX_HASH_LOOKUPS)) + ) { + ;; if the nonce has already been executed, or if it is invalid (== 0), + ;; do not allow verification of the packet + statusCode = UlnConnection::ULN_CONNECTION_VERIFY_FAILED::NONCE_OUT_OF_RANGE; + } elseifnot (_isDvnConfigured(dvnAddress, $ulnReceiveConfigOApp, $defaultUlnReceiveConfigOApp)) { + statusCode = UlnConnection::ULN_CONNECTION_VERIFY_FAILED::DVN_NOT_CONFIGURED; + } else { + ;; Ensure the DVN is not passing a malicious attestation that is bigger than expected + lz::Attestation::validate($attestation); + + setContractStorage( + UlnConnection::utils::setHashLookup( + $storage, + hashLookups, + nonce, + dvnAddress, + $attestation + ) + ); + actions~pushAction( + UlnConnection::event::PACKET_VERIFIED, + $extendedMd + ); + } + + actions~pushAction( + dvnAddress, + UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK, + md::MdObj::build( + md::VerificationStatus::build(nonce, statusCode), + getInitialStorage() + ) + ); + + return actions; +} + +;; removes all attestations for all dvns that are not in the current configuration +tuple garbageCollectInvalidAttestations(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedMdObj = md::MdObj::sanitize($mdObj); + + cell $mdNonce = $sanitizedMdObj.cl::get(md::MdObj::md).md::Nonce::sanitize(); + int nonce = $mdNonce.cl::get(md::Nonce::nonce); + + cell $defaultUlnReceiveConfigOApp = $sanitizedMdObj.cl::get(md::MdObj::obj); + + cell $customUlnReceiveConfigOApp = $storage.cl::get(UlnConnection::UlnReceiveConfigOApp); + (cell requiredDVNs, cell optionalDVNs, int isValid) = UlnReceiveConfig::utils::getVerifyConfig( + $customUlnReceiveConfigOApp, + $defaultUlnReceiveConfigOApp + ); + slice requiredDVNsSlice = requiredDVNs.begin_parse(); + slice optionalDVNsSlice = optionalDVNs.begin_parse(); + + ifnot (isValid) { + return actions; + } + + actions~pushAction( + UlnConnection::event::ATTESTATIONS_EVICTED, + $mdNonce + ); + + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + cell $newStorage = UlnConnection::utils::deleteNonceAttestations($storage, hashLookups, nonce); + + ;; iterate the required DVNs + int requiredDVNAddress = requiredDVNsSlice~AddressList::next(); + while (requiredDVNAddress > NULLADDRESS) { + cell $attestation = UlnConnection::utils::getHashLookup(hashLookups, nonce, requiredDVNAddress); + ifnot (cl::isNullObject($attestation)) { + $newStorage = UlnConnection::utils::setHashLookup( + $newStorage, + hashLookups, + nonce, + requiredDVNAddress, + $attestation + ); + } + requiredDVNAddress = requiredDVNsSlice~AddressList::next(); + } + + int optionalDVNAddress = optionalDVNsSlice~AddressList::next(); + while (optionalDVNAddress > NULLADDRESS) { + cell $attestation = UlnConnection::utils::getHashLookup(hashLookups, nonce, optionalDVNAddress); + ifnot (cl::isNullObject($attestation)) { + $newStorage = UlnConnection::utils::setHashLookup( + $newStorage, + hashLookups, + nonce, + optionalDVNAddress, + $attestation + ); + } + optionalDVNAddress = optionalDVNsSlice~AddressList::next(); + } + + setContractStorage($newStorage); + + return actions; +} + +tuple garbageCollectExecutedNonces(cell $empty) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + int firstUnexecutedNonce = $storage.cl::get(UlnConnection::firstUnexecutedNonce); + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + (int minNonce, _) = cl::dict256::getMin(hashLookups); + + if ((minNonce > 0) & (minNonce < firstUnexecutedNonce)) { + setContractStorage( + UlnConnection::utils::deleteNonceAttestations($storage, hashLookups, minNonce) + ); + actions~pushAction( + getContractAddress(), + UlnConnection::OP::GARBAGE_COLLECT_EXECUTED_NONCES, + $empty + ); + } + + return actions; +} + +;; (uln -> ulnConnection -> channel) +;; @in_md { Packet } +tuple ulnConnectionCommitPacket(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + (cell $packet, cell $defaultUlnReceiveConfig) = $mdObj.md::MdObj::deserialize(); + int packetHash = $packet.cl::hash(); + int nonce = $packet.lz::Packet::getNonce(); + + ( + int endpointAddress, + cell $ulnReceiveConfigOApp, + cell hashLookups + ) = $storage.UlnConnection::getCommitPacketInformation(); + + ( + cell requiredDVNs, + cell optionalDVNs, + int optionalDVNThreshold, + int requiredConfirmations, + int commitPacketGas, + int isValid + ) = UlnReceiveConfig::utils::getCommitConfig( + $ulnReceiveConfigOApp, + $defaultUlnReceiveConfig + ); + + ifnot (isValid) { + return actions; + } + + ifnot ( + _committable(hashLookups, nonce, packetHash, requiredDVNs, optionalDVNs, optionalDVNThreshold, requiredConfirmations) + & ( + (getMsgValue() - _gasToNanoton(get_gas_consumed())) >= _gasToNanoton(commitPacketGas) + ) + ) { + return actions; + } + + ;; If all above checks pass (no early return) + ;; commit the packet to the endpoint + actions~pushAction( + endpointAddress, + Endpoint::OP::ENDPOINT_COMMIT_PACKET, + $packet + ); + + return actions; +} + +;; only channel +tuple msglibConnectionCommitPacketCallback(cell $channelNonceInfo) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + (int committedNonce, int firstUnexecutedNonce) = $channelNonceInfo.md::ChannelNonceInfo::deserialize(); + + cell $commitPOOO = $storage.UlnConnection::getCommitPOOO(); + if (committedNonce <= POOO::maxSettableBit($commitPOOO)) { + $commitPOOO = POOO::set($commitPOOO, committedNonce); + } + + setContractStorage( + $storage.UlnConnection::updateCommittmentInfo(committedNonce, firstUnexecutedNonce, $commitPOOO) + ); + + ;; note that if msglibConnectionCommitPacketCallback is called before msglibConnectionSyncChannelState + ;; is called, and the nonce is greater than the max settable bit, the msglibConnection will be in + ;; a semi-blocked state until msglibConnectionSyncChannelState is called. + ;; best practice is to call msglibConnectionSyncChannelState *before* switching to this msglib + + return actions; +} + +;; only channel +tuple msglibConnectionSyncChannelState(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + $mdObj = $mdObj.md::MdObj::sanitize(); + + cell $channelNonceInfo = $mdObj + .cl::get(md::MdObj::md) + .md::ChannelNonceInfo::sanitize(); + + ;; invariant: + ;; firstUnexecutedNonce >= $storage.cl::get(UlnConnection::firstUnexecutedNonce) + int firstUnexecutedNonce = $channelNonceInfo + .cl::get(md::ChannelNonceInfo::firstUnexecutedNonce); + int firstUncommittedNonce = $channelNonceInfo + .cl::get(md::ChannelNonceInfo::nonce); + + cell $commitPOOO = $storage.cl::get(UlnConnection::commitPOOO); + int maxNonce = POOO::maxSettableBit($commitPOOO); + + if (firstUncommittedNonce > (maxNonce + MAX_CELL_BIT_INDEX)) { + $commitPOOO = POOO::New().cl::set(POOO::nextEmpty, firstUncommittedNonce); + } elseif (firstUncommittedNonce > maxNonce) { + int nextEmpty = $commitPOOO.cl::get(POOO::nextEmpty); + $commitPOOO = POOO::unsafeSetBits($commitPOOO, nextEmpty, maxNonce); + } + + int nextEmpty = $commitPOOO.cl::get(POOO::nextEmpty); + $commitPOOO = POOO::unsafeSetBits($commitPOOO, nextEmpty, firstUncommittedNonce); + + setContractStorage( + $storage + .cl::set( + UlnConnection::firstUnexecutedNonce, + firstUnexecutedNonce + ) + .cl::set(UlnConnection::commitPOOO, $commitPOOO) + ); + + return actions; +} + +tuple setOAppUlnSendConfig(cell $ulnSendConfig) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedUlnSendConfig = UlnSendConfig::sanitize($ulnSendConfig); + + setContractStorage( + $storage.cl::set( + UlnConnection::UlnSendConfigOApp, + $sanitizedUlnSendConfig + ) + ); + + actions~pushAction( + UlnConnection::event::ULN_SEND_CONFIG_SET, + $sanitizedUlnSendConfig + ); + + return actions; +} + +tuple setOAppUlnReceiveConfig(cell $ulnReceiveConfig) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedUlnReceiveConfig = UlnReceiveConfig::sanitize($ulnReceiveConfig); + + setContractStorage( + $storage.cl::set( + UlnConnection::UlnReceiveConfigOApp, + $sanitizedUlnReceiveConfig + ) + ); + + actions~pushAction( + UlnConnection::event::ULN_RECEIVE_CONFIG_SET, + $sanitizedUlnReceiveConfig + ); + + return actions; +} + +(int, int, int) version() impure method_id { + return (3, 0, 2); +} + +cell viewHashLookup(int nonce, int dvnAddress) impure method_id { + cell hashLookups = getContractStorage().cl::get(UlnConnection::hashLookups); + return UlnConnection::utils::getHashLookup(hashLookups, nonce, dvnAddress); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/interface.fc new file mode 100644 index 00000000..164da765 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/interface.fc @@ -0,0 +1,36 @@ +#include "storage.fc"; + +;; authenticated against the config +const int UlnConnection::OP::ULN_CONNECTION_VERIFY = "UlnConnection::OP::ULN_CONNECTION_VERIFY"c; + +;; permissionless +const int UlnConnection::OP::ULN_CONNECTION_COMMIT_PACKET = "UlnConnection::OP::ULN_CONNECTION_COMMIT_PACKET"c; + +const int UlnConnection::OP::SET_OAPP_ULN_RECEIVE_CONFIG = "UlnConnection::OP::SET_OAPP_ULN_RECEIVE_CONFIG"c; +const int UlnConnection::OP::SET_OAPP_ULN_SEND_CONFIG = "UlnConnection::OP::SET_OAPP_ULN_SEND_CONFIG"c; + +const int UlnConnection::OP::GARBAGE_COLLECT_INVALID_ATTESTATIONS = "UlnConnection::OP::GARBAGE_COLLECT_INVALID_ATTESTATIONS"c; + +const int UlnConnection::OP::GARBAGE_COLLECT_EXECUTED_NONCES = "UlnConnection::OP::GARBAGE_COLLECT_EXECUTED_NONCES"c; + +;; ERRORS +const int UlnConnection::ERROR::onlyUln = 193; +const int UlnConnection::ERROR::onlyChannel = 194; +const int UlnConnection::ERROR::invalidUlnSendConfig = 195; +const int UlnConnection::ERROR::onlyConfiguredDvn = 196; + +const int UlnConnection::event::ULN_SEND_CONFIG_SET = "UlnConn::event::ULN_SEND_CFG_SET"u; +const int UlnConnection::event::ULN_RECEIVE_CONFIG_SET = "UlnConn::event::ULN_REC_CFG_SET"u; +const int UlnConnection::event::ATTESTATIONS_EVICTED = "UlnConn::event::ATTESTS_EVCTD"u; +const int UlnConnection::event::PACKET_VERIFIED = "UlnConn::event::PACKET_VERIFIED"u; +const int UlnConnection::event::PACKET_VERIFIED = "UlnConn::event::PACKET_VERIFIED"u; + +const int UlnConnection::ULN_CONNECTION_VERIFY_FAILED::DVN_NOT_CONFIGURED = "UlnConnection::ULN_CONNECTION_VERIFY_FAILED::DVN_NOT_CONFIGURED"c; +const int UlnConnection::ULN_CONNECTION_VERIFY_FAILED::NONCE_OUT_OF_RANGE = "UlnConnection::ULN_CONNECTION_VERIFY_FAILED::NONCE_OUT_OF_RANGE"c; +const int UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED = "UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED"c; + +const int UlnConnection::verificationStatus::VERIFYING = 0; +const int UlnConnection::verificationStatus::COMMITTABLE = 1; +const int UlnConnection::verificationStatus::COMMITTED = 2; +const int UlnConnection::verificationStatus::EXECUTED = 3; +const int UlnConnection::verificationStatus::CONFIGURATION_ERROR = 4; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/main.fc new file mode 100644 index 00000000..9ec38c2a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/main.fc @@ -0,0 +1,32 @@ +#include "../../../core/abstract/protocolMain.fc"; + +#include "../../interface.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == MsglibConnection::OP::MSGLIB_CONNECTION_QUOTE) { + return msglibConnectionQuote($md); + } elseif (op == MsglibConnection::OP::MSGLIB_CONNECTION_SEND) { + return msglibConnectionSend($md); + } elseif (op == UlnConnection::OP::ULN_CONNECTION_VERIFY) { + return ulnConnectionVerify($md); + } elseif (op == UlnConnection::OP::ULN_CONNECTION_COMMIT_PACKET) { + return ulnConnectionCommitPacket($md); + } elseif (op == MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK) { + return msglibConnectionCommitPacketCallback($md); + } elseif (op == MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE) { + return msglibConnectionSyncChannelState($md); + } elseif (op == UlnConnection::OP::SET_OAPP_ULN_SEND_CONFIG) { + return setOAppUlnSendConfig($md); + } elseif (op == UlnConnection::OP::SET_OAPP_ULN_RECEIVE_CONFIG) { + return setOAppUlnReceiveConfig($md); + } elseif (op == UlnConnection::OP::GARBAGE_COLLECT_INVALID_ATTESTATIONS) { + return garbageCollectInvalidAttestations($md); + } elseif (op == UlnConnection::OP::GARBAGE_COLLECT_EXECUTED_NONCES) { + return garbageCollectExecutedNonces($md); + } + throw(BaseInterface::ERROR::invalidOpcode); + return null(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/storage.fc new file mode 100644 index 00000000..f18780ad --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/storage.fc @@ -0,0 +1,156 @@ +#include "interface.fc"; + +#include "../msgdata/UlnReceiveConfig.fc"; +#include "../msgdata/UlnSendConfig.fc"; + +#include "../../../core/baseStorage.fc"; + +#include "../../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; + +const int UlnConnection::MAX_HASH_LOOKUPS = 1023; + +const int UlnConnection::NAME = "connection"u; + +const int UlnConnection::baseStorage = 0; +const int UlnConnection::path = 1; +;; the channel address is set during the initialize call +const int UlnConnection::endpointAddress = 2; +const int UlnConnection::channelAddress = 3; +const int UlnConnection::firstUnexecutedNonce = 4; ;; first nonce that has not been executed +const int UlnConnection::ulnAddress = 5; + +;; EVM struct ExecutorConfig +;; uint32 maxMessageSize; +;; address executor; +const int UlnConnection::UlnSendConfigOApp = 6; ;; UlnSendConfig +const int UlnConnection::UlnReceiveConfigOApp = 7; ;; UlnReceiveConfig +;; breaking from EVM, we are using a fixed size array for the DVN verifications, and DVNs can +;; only verify a packet for a specific inbound nonce range. +const int UlnConnection::hashLookups = 8; ;; map nonce -> map dvnAddress -> {hash,confirmations} +const int UlnConnection::commitPOOO = 9; + +;; @owner ulnManager +cell UlnConnection::New(int owner, cell $path, int ulnAddress) impure method_id { + return cl::declare( + UlnConnection::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; UlnConnection::baseStorage + [cl::t::objRef, $path], ;; UlnConnection::path + [cl::t::address, NULLADDRESS], ;; UlnConnection::endpointAddress + [cl::t::address, NULLADDRESS], ;; UlnConnection::channelAddress + [cl::t::uint64, 1], ;; UlnConnection::firstUnexecutedNonce + [cl::t::address, ulnAddress], ;; UlnConnection::ulnAddress + [cl::t::objRef, UlnSendConfig::NewWithDefaults()], ;; UlnConnection::UlnSendConfigOApp + [cl::t::objRef, UlnReceiveConfig::NewWithDefaults()], ;; UlnConnection::UlnReceiveConfigOApp + [cl::t::dict256, cl::dict256::New()], ;; UlnConnection::hashLookups + [cl::t::objRef, empty_cell()] ;; UlnConnection::commitPOOO + ]) + ); +} + +;; ====================== Storage Accessors ===================== + + +const int UlnConnection::_endpointAddressOffset = _HEADER_WIDTH; +const int UlnConnection::_channelAddressOffset = UlnConnection::_endpointAddressOffset + 256; +const int UlnConnection::_firstUnexecutedNonceOffset = UlnConnection::_channelAddressOffset + 256; +const int UlnConnection::_rootSliceBits = UlnConnection::_firstUnexecutedNonceOffset + 64; + +const int UlnConnection::_ulnAddressOffset = 0; + +cell UlnConnection::getBaseStorage(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +int UlnConnection::getChannelAddress(cell $self) impure inline { + return $self.cellPreloadAddressAt(UlnConnection::_channelAddressOffset); +} + +int UlnConnection::getUlnAddress(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadAddressAt(UlnConnection::_ulnAddressOffset); +} + +cell UlnConnection::getCommitPOOO(cell $self) impure inline { + return $self.cellPreloadRefAt(2).cellPreloadRefAt(3); +} + +;; ====================== Composite Storage Accessors ===================== + +;; (ulnAddress, channelAddress, ulnSendConfigOApp) +(int, int, cell) UlnConnection::getSendInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2_slice = selfSlice.preloadRefSliceAt(2); + return ( + ref2_slice.preloadAddressAt(UlnConnection::_ulnAddressOffset), + selfSlice.preloadAddressAt(UlnConnection::_channelAddressOffset), + ref2_slice.preloadRefAt(0) + ); +} + +;; (firstUnexecutedNonce, hashLookups, ulnReceiveConfigOApp) +(int, cell, cell) UlnConnection::getVerifyInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2Slice = selfSlice.preloadRefSliceAt(2); + + return ( + selfSlice.preloadUint64At(UlnConnection::_firstUnexecutedNonceOffset), + ref2Slice.preloadRefAt(2), + ref2Slice.preloadRefAt(1) + ); +} + +;; (endpointAddress, ulnReceiveConfigOApp, hashLookups) +(int, cell, cell) UlnConnection::getCommitPacketInformation(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + slice ref2Slice = selfSlice.preloadRefSliceAt(2); + return ( + selfSlice.preloadAddressAt(UlnConnection::_endpointAddressOffset), + ref2Slice.preloadRefAt(1), + ref2Slice.preloadRefAt(2) + ); +} + +;; ====================== Storage Modifiers ===================== + + +cell UlnConnection::updateCommittmentInfo( + cell $self, + int committedNonce, + int firstUnexecutedNonce, + cell $commitPOOO +) impure inline { + slice selfSlice = $self.begin_parse(); + + slice ref2Slice = selfSlice.preloadRefSliceAt(2); + + cell hashLookups = ref2Slice.preloadRefAt(2).cl::dict256::delete(committedNonce); + + cell new_ref2 = begin_cell() + .store_slice(ref2Slice.scutfirst(256, 2)) + .store_ref(hashLookups) + .store_ref($commitPOOO) + .end_cell(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(UlnConnection::_firstUnexecutedNonceOffset, 2)) + .store_uint64(firstUnexecutedNonce) + .store_ref(new_ref2) + .end_cell(); +} + +cell UlnConnection::setHashLookups(cell $self, cell $hashLookups) impure inline { + slice selfSlice = $self.begin_parse(); + + slice originalStructuralCellSlice = selfSlice.preloadRefSliceAt(2); + + cell newStructuralCell = begin_cell() + .store_slice(originalStructuralCellSlice.scutfirst(256, 2)) + .store_ref($hashLookups) + .store_ref(originalStructuralCellSlice.preloadRefAt(3)) + .end_cell(); + + return begin_cell() + .store_slice(selfSlice.scutfirst(UlnConnection::_rootSliceBits, 2)) + .store_ref(newStructuralCell) + .end_cell(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/main.fc new file mode 100644 index 00000000..7717c09f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/main.fc @@ -0,0 +1,1851 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../utils.fc"; + +#include "../../callbackOpcodes.fc"; +#include "../../msgdata/Attestation.fc"; +#include "../../msgdata/InitUlnConnection.fc"; +#include "../../msgdata/UlnReceiveConfig.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnVerification.fc"; +#include "../../uln/interface.fc"; + +#include "../../../../interfaces.fc"; + +#include "../../../../../classes/lz/Path.fc"; +#include "../../../../../classes/msgdata/ChannelNonceInfo.fc"; +#include "../../../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../funC++/actions/call.fc"; +#include "../../../../../funC++/actions/utils.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../funC++/stdlib.fc"; +#include "../../../../../funC++/stringlib.fc"; +#include "../../../../../funC++/testutils.fc"; +#include "../../../../../funC++/utils.fc"; + +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; + +slice _testName() { return "connection"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUlnConnection::New( + UlnSendConfig::NewWithDefaults(), + UlnReceiveConfig::NewWithDefaults(), + ENDPOINT_ADDRESS, + CHANNEL_ADDRESS + ) + ); +} + +cell createContractStorage() impure { + cell $path = lz::Path::New(SRC_EID, getContractAddress(), DST_EID, DST_OAPP); + setContractStorage( + UlnConnection::New(ULN_MANAGER_ADDRESS, $path, ULN_ADDRESS) + ); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= +(int, slice) _initialize::success::basic(cell $storage) impure { + + cell $expectedULNSendConfigOApp = UlnSendConfig::NewWithDefaults(); + cell $expectedUlnReceiveConfigOApp = UlnReceiveConfig::NewWithDefaults(); + + cell $actualSendUlnReceiveConfigOApp = $storage.cl::get(UlnConnection::UlnSendConfigOApp); + cell $actualReceiveUlnReceiveConfigOApp = $storage.cl::get(UlnConnection::UlnReceiveConfigOApp); + + return test::shouldBeTrue( + ($actualSendUlnReceiveConfigOApp.cl::hash() == $expectedULNSendConfigOApp.cl::hash()) + & ($actualReceiveUlnReceiveConfigOApp.cl::hash() == $expectedUlnReceiveConfigOApp.cl::hash()) + ); +} + +(int, slice) UlnConnection::utils::getDVNsAndConf::fail(cell $storage) impure { + cell $badDefaults = UlnReceiveConfig::NewWithDefaults(); + int failed = false; + try { + (_, _, _, int confirmations, _, _) = UlnReceiveConfig::utils::getCommitConfig( + $storage.cl::get(UlnConnection::UlnReceiveConfigOApp), + $badDefaults + ); + if (confirmations == DEFAULT_CONFIRMATIONS) { + failed = true; + return (TEST_FAILED, "never throws"); + } + } catch(_, n) { + if (n != UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR) { + return ( + TEST_FAILED, + "actual error: " + .str::concatInt(n) + .str::concat(" != expected: ") + .str::concatInt(UlnReceiveConfig::ERROR::DVN_CONFIG_ERROR) + ); + } + } + return (TEST_SUCCESS, ""); +} + +(int, slice) UlnConnection::setHashLookup::success(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + tuple requiredDVNAddresses = _receiveDvnListToTuple( + $defaultUlnReceiveConfig.cl::get(UlnReceiveConfig::requiredDVNs) + ); + int requiredDVN0Address = requiredDVNAddresses.int_at(0); + + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + $storage = UlnConnection::utils::setHashLookup( + $storage, + hashLookups, + NONCE, + requiredDVN0Address, + lz::Attestation::New(PACKET_HASH, DEFAULT_CONFIRMATIONS) + ); + + hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + ;; verify the hash was properly added + cell $attestation = UlnConnection::utils::getHashLookup(hashLookups, NONCE, requiredDVN0Address); + return test::shouldBeTrue( + ($attestation.cell_is_empty() == false) + & ($attestation.cl::get(lz::Attestation::hash) == PACKET_HASH) + ); +} + +(int, slice) setOAppUlnSendConfig::success::basic(cell $storage) impure { + cell $ulnSendConfig = MOCK_CUSTOM_ULN_SEND_CONFIG(); + return test::handler::shouldPass( + setOAppUlnSendConfig, + $ulnSendConfig, + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::ULN_SEND_CONFIG_SET, + $ulnSendConfig + ) + ]), + $storage.cl::set(UlnConnection::UlnSendConfigOApp, $ulnSendConfig), + txnContext + ); +} + +(int, slice) setOAppUlnSendConfig::fail::nullAddress::dvnConfigError(cell $storage) impure { + cell $ulnSendConfig = UlnSendConfig::New( + CUSTOM_WORKER_QUOTE_GAS_LIMIT, + CUSTOM_MAX_MESSAGE_BYTES, + true, + CUSTOM_EXECUTOR, + true, + MOCK_DVN_CELL_WITH_NULL(2), + true, + MOCK_DEFAULT_OPTIONAL_DVN_CELL(2, 0), + true, + DEFAULT_CONFIRMATIONS + ); + + return test::handler::shouldFail( + setOAppUlnSendConfig, + $ulnSendConfig, + UlnSendConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setOAppUlnSendConfig::fail::maxDVNCount::dvnConfigError(cell $storage) impure { + cell $ulnSendConfig = UlnSendConfig::New( + CUSTOM_WORKER_QUOTE_GAS_LIMIT, + CUSTOM_MAX_MESSAGE_BYTES, + true, + CUSTOM_EXECUTOR, + true, + MOCK_CUSTOM_REQUIRED_DVN_CELL(256, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + true, + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + true, + DEFAULT_CONFIRMATIONS + ); + + return test::handler::shouldFail( + setOAppUlnSendConfig, + $ulnSendConfig, + UlnSendConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setOAppUlnSendConfig::fail::tooManyRefs::dvnConfigError(cell $storage) impure { + cell $ulnSendConfig = UlnSendConfig::New( + CUSTOM_WORKER_QUOTE_GAS_LIMIT, + CUSTOM_MAX_MESSAGE_BYTES, + true, + CUSTOM_EXECUTOR, + true, + MOCK_DVN_LIST_WITH_REF(2), + true, + MOCK_DEFAULT_OPTIONAL_DVN_CELL(2, 0), + true, + DEFAULT_CONFIRMATIONS + ); + + return test::handler::shouldFail( + setOAppUlnSendConfig, + $ulnSendConfig, + UlnSendConfig::ERROR::DVN_CONFIG_ERROR + ); +} + +(int, slice) setOAppUlnReceiveConfig::success::basic(cell $storage) impure { + cell $ulnReceiveConfig = MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0); + return test::handler::shouldPass( + setOAppUlnReceiveConfig, + $ulnReceiveConfig, + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::ULN_RECEIVE_CONFIG_SET, + $ulnReceiveConfig + ) + ]), + $storage.cl::set(UlnConnection::UlnReceiveConfigOApp, $ulnReceiveConfig), + txnContext + ); +} + +(int, slice) setOAppUlnReceiveConfig::fail::invalidMinCommitPacketGas(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + 0, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldFail( + setOAppUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::INVALID_MIN_COMMIT_PACKET_GAS + ); +} + +(int, slice) setOAppUlnReceiveConfig::fail::optionalThresholdTooLarge(cell $storage) impure { + cell $ulnReceiveConfig = MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 1, 2); + + return test::handler::shouldFail( + setOAppUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LARGE + ); +} + +(int, slice) setOAppUlnReceiveConfig::fail::optionalThresholdTooLow(cell $storage) impure { + cell $ulnReceiveConfig = MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 2, 0); + + return test::handler::shouldFail( + setOAppUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::OPTIONAL_THRESHOLD_TOO_LOW + ); +} + +(int, slice) setOAppUlnReceiveConfig::fail::dvnCountsAllNil(cell $storage) impure { + cell $ulnReceiveConfig = UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + false, + _buildReceiveDvnList(0, MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0)), + 0 + ); + + return test::handler::shouldFail( + setOAppUlnReceiveConfig, + $ulnReceiveConfig, + UlnReceiveConfig::ERROR::DVN_COUNTS_ALL_NIL + ); +} + +;;; ===============================ULN CONNECTION VERIFY=========================== + +(int, slice) ulnConnectionVerify::success::basic(cell $storage) impure { + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + $storage = getContractStorage(); + cell $ulnVerificationMd = MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS); + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + cell $extendedMd = md::ExtendedMd::New( + $ulnVerificationMd, + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ); + return test::handler::shouldPass( + ulnConnectionVerify, + $extendedMd, + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::PACKET_VERIFIED, + $extendedMd + ), + _newAction( + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK, + md::MdObj::New( + md::VerificationStatus::New(NONCE, UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED), + getInitialStorage() + ) + ) + ]), + UlnConnection::utils::setHashLookup( + $storage, + hashLookups, + NONCE, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + $ulnVerificationMd.cl::get(md::UlnVerification::attestation) + ), + txnContext + ); +} + +(int, slice) ulnConnectionVerify::success::minVerifiableNonce(cell $storage) impure { + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + setContractStorage( + getContractStorage().cl::set(UlnConnection::firstUnexecutedNonce, NONCE) + ); + $storage = getContractStorage(); + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + cell $ulnVerificationMd = MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS); + cell $extendedMd = md::ExtendedMd::New( + $ulnVerificationMd, + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ); + return test::handler::shouldPass( + ulnConnectionVerify, + $extendedMd, + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::PACKET_VERIFIED, + $extendedMd + ), + _newAction( + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK, + md::MdObj::New( + md::VerificationStatus::New(NONCE, UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED), + getInitialStorage() + ) + ) + ]), + UlnConnection::utils::setHashLookup( + $storage, + hashLookups, + NONCE, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + $ulnVerificationMd.cl::get(md::UlnVerification::attestation) + ), + txnContext + ); +} + +(int, slice) ulnConnectionVerify::success::maxVerifiableNonce(cell $storage) impure { + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + cell $storage = getContractStorage(); + + int firstUnexecutedNonce = $storage.cl::get(UlnConnection::firstUnexecutedNonce); + int maxVerifiableNonce = firstUnexecutedNonce + UlnConnection::MAX_HASH_LOOKUPS - 1; + + cell $ulnVerification = MOCK_ULN_VERIFICATION_FULL( + maxVerifiableNonce, + DEFAULT_CONFIRMATIONS, + MOCK_RECEIVE_PACKET_WITH_NONCE(maxVerifiableNonce).cl::hash() + ); + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + cell $extendedMd = md::ExtendedMd::New( + $ulnVerification, + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ); + + return test::handler::shouldPass( + ulnConnectionVerify, + $extendedMd, + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::PACKET_VERIFIED, + $extendedMd + ), + _newAction( + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK, + md::MdObj::New( + md::VerificationStatus::New(maxVerifiableNonce, UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED), + getInitialStorage() + ) + ) + ]), + UlnConnection::utils::setHashLookup( + $storage, + hashLookups, + maxVerifiableNonce, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + $ulnVerification.cl::get(md::UlnVerification::attestation) + ), + txnContext + ); +} + +(int, slice) ulnConnectionVerify::success::twoNonces(cell $storage) impure { + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION_FULL( + NONCE + 1, + DEFAULT_CONFIRMATIONS, + MOCK_RECEIVE_PACKET_WITH_NONCE(1) + .cl::hash() + ), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ) + ); + + $storage = getContractStorage(); + cell $ulnVerificationMd = MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS); + cell $extendedMd = md::ExtendedMd::New( + $ulnVerificationMd, + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ); + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + return test::handler::shouldPass( + ulnConnectionVerify, + $extendedMd, + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::PACKET_VERIFIED, + $extendedMd + ), + _newAction( + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK, + md::MdObj::New( + md::VerificationStatus::New(NONCE, UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED), + getInitialStorage() + ) + ) + ]), + UlnConnection::utils::setHashLookup( + $storage, + hashLookups, + NONCE, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + $ulnVerificationMd.cl::get(md::UlnVerification::attestation) + ), + txnContext + ); +} + +(int, slice) ulnConnectionVerify::fail::nonceTooLarge(cell $storage) impure { + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + + int firstUnexecutedNonce = $storage.cl::get(UlnConnection::firstUnexecutedNonce); + int tooLargeNonce = firstUnexecutedNonce + UlnConnection::MAX_HASH_LOOKUPS; + + return test::handler::shouldPass( + ulnConnectionVerify, + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION_FULL( + tooLargeNonce, + DEFAULT_CONFIRMATIONS, + MOCK_RECEIVE_PACKET_WITH_NONCE(tooLargeNonce).cl::hash() + ), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ), + unsafeTuple([ + 0, + _newAction( + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK, + md::MdObj::New( + md::VerificationStatus::New( + tooLargeNonce, + UlnConnection::ULN_CONNECTION_VERIFY_FAILED::NONCE_OUT_OF_RANGE + ), + getInitialStorage() + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnConnectionVerify::fail::nonceTooSmall(cell $storage) impure { + int alreadyExecutedNonce = NONCE; + setContractStorage( + $storage.cl::set(UlnConnection::firstUnexecutedNonce, alreadyExecutedNonce + 1) + ); + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + + return test::handler::shouldPass( + ulnConnectionVerify, + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION_FULL( + alreadyExecutedNonce, + DEFAULT_CONFIRMATIONS, + MOCK_RECEIVE_PACKET_WITH_NONCE(alreadyExecutedNonce).cl::hash() + ), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ), + unsafeTuple([ + 0, + _newAction( + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK, + md::MdObj::New( + md::VerificationStatus::New( + alreadyExecutedNonce, + UlnConnection::ULN_CONNECTION_VERIFY_FAILED::NONCE_OUT_OF_RANGE + ), + getInitialStorage() + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnConnectionVerify::fail::wrongDVN(cell $storage) impure { + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + + return test::handler::shouldPass( + ulnConnectionVerify, + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + ATTACKER_ADDRESS + ), + unsafeTuple([ + 0, + _newAction( + ATTACKER_ADDRESS, + UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK, + md::MdObj::New( + md::VerificationStatus::New( + NONCE, + UlnConnection::ULN_CONNECTION_VERIFY_FAILED::DVN_NOT_CONFIGURED + ), + getInitialStorage() + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnConnectionVerify::revert::malformedAttestationStructure(cell $storage) impure { + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + int addr = MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0); + + cell $verification = cl::declare( + md::UlnVerification::NAME, + unsafeTuple([ + [cl::t::uint64, NONCE], + [cl::t::objRef, _dupWithGarbage(lz::Attestation::New( + MOCK_RECEIVE_PACKET().cl::hash(), + DEFAULT_CONFIRMATIONS + ))] + ]) + ); + + return test::handler::shouldFail( + ulnConnectionVerify, + md::ExtendedMd::New( + _dupWithGarbage($verification), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + addr + ), + cl::ERROR::MALFORMED_OBJECT + ); +} + +(int, slice) ulnConnectionVerify::revert::malformedAttestationHeader(cell $storage) impure { + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + int addr = MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0); + cell $verification = cl::declare( + md::UlnVerification::NAME, + unsafeTuple([ + [cl::t::uint64, NONCE], + [cl::t::objRef, headerCellGarbage(lz::Attestation::New( + MOCK_RECEIVE_PACKET().cl::hash(), + DEFAULT_CONFIRMATIONS + ))] + ]) + ); + return test::handler::shouldFail( + ulnConnectionVerify, + md::ExtendedMd::New( + _dupWithGarbage($verification), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + addr + ), + cl::ERROR::MALFORMED_OBJECT + ); +} + +(int, slice) ulnConnectionVerify::success::partiallyMalformedAttestationHeader(cell $storage) impure { + setOAppUlnReceiveConfig(MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0)); + $storage = getContractStorage(); + + slice attest = cl::declare( + lz::Attestation::NAME, + unsafeTuple([ + [cl::t::uint256, MOCK_RECEIVE_PACKET().cl::hash()], + [cl::t::uint64, DEFAULT_CONFIRMATIONS] + ]) + ).begin_parse(); + + attest = attest.scutfirst(256, attest.slice_refs()); + builder attestB = begin_cell().store_slice(attest); + + int ind = 0; + while (ind < (_HEADER_WIDTH + 256 + 64) - 256) { + attestB = attestB.store_bool(0); + ind += 1; + } + + cell $attestation = attestB.end_cell(); + + cell $verification = cl::declare( + md::UlnVerification::NAME, + unsafeTuple([ + [cl::t::uint64, NONCE], + [cl::t::objRef, $attestation] + ]) + ); + cell $extendedMd = md::ExtendedMd::New( + $verification, + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0) + ); + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + return test::handler::shouldPass( + ulnConnectionVerify, + $extendedMd, + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::PACKET_VERIFIED, + $extendedMd + ), + _newAction( + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK, + md::MdObj::New( + md::VerificationStatus::New(NONCE, UlnConnection::ULN_CONNECTION_VERIFY_SUCCEEDED), + getInitialStorage() + ) + ) + ]), + UlnConnection::utils::setHashLookup( + $storage, + hashLookups, + NONCE, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(0), + $attestation + ), + txnContext + ); +} + +;;; ===============================ULN CONNECTION COMMIT PACKET=========================== + +(int, slice) ulnConnectionCommitPacket::success::partiallyMalformedAttestationHeader(cell $storage) impure { + cell $defaultUlnReceiveConfig = headerCellGarbage(MOCK_DEFAULT_ULN_RECEIVE_CONFIG()); + + ;; required dvns verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + $defaultUlnReceiveConfig + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + unsafeTuple([ + 0, + _newAction( + ENDPOINT_ADDRESS, + Endpoint::OP::ENDPOINT_COMMIT_PACKET, + MOCK_RECEIVE_PACKET() + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnConnectionCommitPacket::success::basic(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + ;; required dvns verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + $defaultUlnReceiveConfig + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + unsafeTuple([ + 0, + _newAction( + ENDPOINT_ADDRESS, + Endpoint::OP::ENDPOINT_COMMIT_PACKET, + MOCK_RECEIVE_PACKET() + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnConnectionCommitPacket::success::withOptionalDVNs(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG_WITH_OPTIONAL_DVNS(1, 1); + + ;; required dvns verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + ;; optional dvns verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + $defaultUlnReceiveConfig + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + unsafeTuple([ + 0, + _newAction( + ENDPOINT_ADDRESS, + Endpoint::OP::ENDPOINT_COMMIT_PACKET, + MOCK_RECEIVE_PACKET() + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; packet commit comes in with the same nonce, but different hash +(int, slice) ulnConnectionCommitPacket::fail::wrongPacket(cell $storage) impure { + ;; required dvns verify the wrong packet hash + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION_FULL(NONCE, DEFAULT_CONFIRMATIONS, MOCK_RECEIVE_PACKET().cl::hash()), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_SEND_PACKET(), ;; same nonce as receive packet, but different (path => hash) + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + emptyActions(), + getContractStorage(), + txnContext + ); +} + +;; none of the required DVNs have attested this packet, only some of the optional DVNs have +(int, slice) ulnConnectionCommitPacket::fail::missingAllRequiredDVNs(cell $storage) impure { + cell $customUlnReceiveConfig = MOCK_CUSTOM_ULN_RECEIVE_CONFIG(DEFAULT_REQUIRED_DVNS, 1, 1); + + ;; optional dvns verify the wrong packet hash + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $customUlnReceiveConfig, + MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(0) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + $customUlnReceiveConfig + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + unsafeTuple([0]), + getContractStorage(), + txnContext + ); +} + +;; some of the required DVNs have attested this packet, but not enough of them +(int, slice) ulnConnectionCommitPacket::fail::missingSomeRequiredDVNs(cell $storage) impure { + ;; not enough required dvns verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + emptyActions(), + getContractStorage(), + txnContext + ); +} + +;; there are no attestations anywhere for this packet +(int, slice) ulnConnectionCommitPacket::fail::noAttestation(cell $storage) impure { + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + emptyActions(), + getContractStorage(), + txnContext + ); +} + +;; one of the default DVNs doesn't have enough confirmations +(int, slice) ulnConnectionCommitPacket::fail::notEnoughDefaultDVNConfirmations(cell $storage) impure { + ;; required dvns verify with not enough confirmations + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS - 1), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS - 2), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnConnectionCommitPacket::fail::notEnoughOptionalDVNConfirmations(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG_WITH_OPTIONAL_DVNS(2, 2); + + ;; the default dvns do verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(CUSTOM_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + ;; only one optional dvn verifies + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(CUSTOM_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + $defaultUlnReceiveConfig + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnConnectionCommitPacket::success::someOptionalDVNsHaveWrongPacketHash(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG_WITH_OPTIONAL_DVNS(3, 2); + + ;; default dvns verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + ;; optional dvns verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(CUSTOM_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(0) + ) + ); + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(CUSTOM_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(1) + ) + ); + + ;; the last optional dvn has the wrong packet hash + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION_FULL(NONCE, CUSTOM_CONFIRMATIONS, MOCK_SEND_PACKET().cl::hash()), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(2) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + $defaultUlnReceiveConfig + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + unsafeTuple([ + 0, + _newAction( + ENDPOINT_ADDRESS, + Endpoint::OP::ENDPOINT_COMMIT_PACKET, + MOCK_RECEIVE_PACKET() + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) ulnConnectionCommitPacket::fail::notEnoughCommitPacketGas(cell $storage) impure { + txnContext~tset(_MSG_VALUE, _gasToNanoton(DEFAULT_MIN_COMMIT_PACKET_GAS) + 100); + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + ;; the default dvns do verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(CUSTOM_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(CUSTOM_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + cell $mdObj = md::MdObj::New( + MOCK_RECEIVE_PACKET(), + $defaultUlnReceiveConfig + ); + + return test::handler::shouldPass( + ulnConnectionCommitPacket, + $mdObj, + emptyActions(), + getContractStorage(), + txnContext + ); +} + +;;; ===============================ULN CONNECTION COMMIT PACKET CALLBACK=========================== + +(int, slice) msglibConnectionCommitPacketCallback::success::basic(cell $storage) impure { + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + $storage = getContractStorage(); + cell $channelNonceInfo = md::ChannelNonceInfo::New( + NONCE, + NONCE - 1 + ); + + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + return test::handler::shouldPass( + msglibConnectionCommitPacketCallback, + $channelNonceInfo, + emptyActions(), + $storage + .UlnConnection::utils::deleteNonceAttestations(hashLookups, NONCE) + .cl::set(UlnConnection::firstUnexecutedNonce, NONCE - 1) + .cl::set(UlnConnection::commitPOOO, POOO::New().POOO::set(NONCE)), + txnContext + ); +} + +;; The callback for a given packet can be received multiple times +(int, slice) msglibConnectionCommitPacketCallback::success::notifyPacketExecuted(cell $storage) impure { + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + $storage = getContractStorage(); + ;; firstUnexecutedNonce can be greater than the current nonce if + ;; the first commitPacketCallback failed + cell $channelNonceInfo = md::ChannelNonceInfo::New(NONCE, NONCE + 1); + + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + return test::handler::shouldPass( + msglibConnectionCommitPacketCallback, + $channelNonceInfo, + emptyActions(), + $storage + .UlnConnection::utils::deleteNonceAttestations(hashLookups, NONCE) + .cl::set(UlnConnection::firstUnexecutedNonce, NONCE + 1) + .cl::set(UlnConnection::commitPOOO, POOO::New().POOO::set(NONCE)), + txnContext + ); +} + +(int, slice) msglibConnectionCommitPacketCallback::success::idempotent(cell $storage) impure { + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + msglibConnectionCommitPacketCallback( + md::ChannelNonceInfo::New(NONCE, NONCE - 1) + ); + + $storage = getContractStorage(); + ;; firstUnexecutedNonce can be greater than the current nonce if + ;; the first commitPacketCallback failed + cell $channelNonceInfo = md::ChannelNonceInfo::New(NONCE, NONCE + 1); + + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + return test::handler::shouldPass( + msglibConnectionCommitPacketCallback, + $channelNonceInfo, + emptyActions(), + $storage + .UlnConnection::utils::deleteNonceAttestations(hashLookups, NONCE) + .cl::set(UlnConnection::firstUnexecutedNonce, NONCE + 1), + txnContext + ); +} +;;; ===============================EVICT ATTESATION=========================== + +(int, slice) garbageCollectInvalidAttestations::success::basic(cell $storage) impure { + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + return test::handler::shouldPass( + garbageCollectInvalidAttestations, + md::MdObj::New( + md::Nonce::New(NONCE), + MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0) + ), + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::ATTESTATIONS_EVICTED, + md::Nonce::New(NONCE) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) garbageCollectInvalidAttestations::success::maxDVNs(cell $storage) impure { + int numDVNs = 8; + cell $ulnReceiveConfig = MOCK_CUSTOM_ULN_RECEIVE_CONFIG( + UlnReceiveConfig::MaxRequiredDVNs, + UlnReceiveConfig::MaxOptionalDVNs, + UlnReceiveConfig::MaxOptionalDVNs + ); + throw_if(1, numDVNs > UlnReceiveConfig::MaxRequiredDVNs); + int idx = 0; + while (idx < numDVNs) { + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $ulnReceiveConfig, + MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(idx) + ) + ); + idx += 1; + } + + return test::handler::shouldPass( + garbageCollectInvalidAttestations, + md::MdObj::New( + md::Nonce::New(NONCE), + $ulnReceiveConfig + ), + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::ATTESTATIONS_EVICTED, + md::Nonce::New(NONCE) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) garbageCollectInvalidAttestations::success::noEvictedDvns(cell $storage) impure { + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + return test::handler::shouldPass( + garbageCollectInvalidAttestations, + md::MdObj::New( + md::Nonce::New(NONCE), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ), + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::ATTESTATIONS_EVICTED, + md::Nonce::New(NONCE) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) garbageCollectInvalidAttestations::success::noAttestation(cell $storage) impure { + return test::handler::shouldPass( + garbageCollectInvalidAttestations, + md::MdObj::New( + md::Nonce::New(NONCE), + MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0) + ), + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::ATTESTATIONS_EVICTED, + md::Nonce::New(NONCE) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) garbageCollectExecutedNonces::success::noExecutedNonce(cell $storage) impure { + setContractStorage( + $storage.cl::set(UlnConnection::firstUnexecutedNonce, 0) + ); + + return test::handler::shouldPass( + garbageCollectExecutedNonces, + cl::nullObject(), + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) garbageCollectExecutedNonces::success::noGarbageCollected(cell $storage) impure { + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + setContractStorage( + getContractStorage().cl::set(UlnConnection::firstUnexecutedNonce, NONCE) + ); + + return test::handler::shouldPass( + garbageCollectExecutedNonces, + cl::nullObject(), + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) garbageCollectExecutedNonces::success::collectOne(cell $storage) impure { + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + + setContractStorage( + getContractStorage().cl::set(UlnConnection::firstUnexecutedNonce, NONCE + 1) + ); + + return test::handler::shouldPass( + garbageCollectExecutedNonces, + cl::nullObject(), + unsafeTuple([ + 0, + _newAction( + getContractAddress(), + UlnConnection::OP::GARBAGE_COLLECT_EXECUTED_NONCES, + cl::nullObject() + ) + ]), + ;; "rollback" the verify call + $storage.cl::set(UlnConnection::firstUnexecutedNonce, NONCE + 1), + txnContext + ); +} + +(int, slice) setOAppUlnSendConfig::success::malicious(cell $storage) impure { + cell $ulnSendConfig = MOCK_CUSTOM_ULN_SEND_CONFIG(); + cell $maliciousUlnSendConfig = _dupWithGarbage($ulnSendConfig); + return test::handler::shouldPass( + setOAppUlnSendConfig, + $maliciousUlnSendConfig, + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::ULN_SEND_CONFIG_SET, + $ulnSendConfig + ) + ]), + $storage.cl::set(UlnConnection::UlnSendConfigOApp, $ulnSendConfig), + txnContext + ); +} + +(int, slice) setOAppUlnReceiveConfig::success::malicious(cell $storage) impure { + cell $ulnReceiveConfig = MOCK_CUSTOM_ULN_RECEIVE_CONFIG(1, 0, 0); + cell $maliciousUlnReceiveConfig = _dupWithGarbage($ulnReceiveConfig); + return test::handler::shouldPass( + setOAppUlnReceiveConfig, + $maliciousUlnReceiveConfig, + unsafeTuple([ + 0, + _newAction( + UlnConnection::event::ULN_RECEIVE_CONFIG_SET, + $ulnReceiveConfig + ) + ]), + $storage.cl::set(UlnConnection::UlnReceiveConfigOApp, $ulnReceiveConfig), + txnContext + ); +} + +(int, slice) msglibConnectionSend::success::basic(cell $storage) impure { + cell $LzSend = MOCK_SEND_PACKET(); + + return test::handler::shouldPass( + msglibConnectionSend, + $LzSend, + unsafeTuple([ + 0, + _newAction( + ULN_ADDRESS, + Uln::OP::ULN_SEND, + md::UlnSend::New( + $LzSend, + $storage.cl::get(UlnConnection::UlnSendConfigOApp), + getInitialStorage(), + $storage.cl::get
(UlnConnection::channelAddress) + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) msglibConnectionSyncChannelState::success::basic(cell $storage) impure { + cell $channelNonceInfo = md::ChannelNonceInfo::New(NONCE, NONCE - 10); + return test::handler::shouldPass( + msglibConnectionSyncChannelState, + md::MdObj::New($channelNonceInfo, getInitialStorage()), + emptyActions(), + $storage + .cl::set(UlnConnection::firstUnexecutedNonce, NONCE - 10) + .cl::set(UlnConnection::commitPOOO, POOO::unsafeSetBits(POOO::New(), 1, NONCE)), + txnContext + ); +} + +(int, slice) msglibConnectionQuote::success::basic(cell $storage) impure { + cell $LzSend = MOCK_SEND_PACKET(); + return test::handler::shouldPass( + msglibConnectionQuote, + $LzSend, + unsafeTuple([ + 0, + _newAction( + ULN_ADDRESS, + Uln::OP::ULN_QUOTE, + md::UlnSend::New( + $LzSend, + $storage.cl::get(UlnConnection::UlnSendConfigOApp), + getInitialStorage(), + getCaller() + ) + ) + ]), + $storage, + txnContext + ); +} + +(int, slice) committableView::success::basic(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + cell $packet = MOCK_RECEIVE_PACKET(); + int nonce = NONCE; + + return test::shouldBeTrue( + committableView(nonce, $packet, $defaultUlnReceiveConfig) == UlnConnection::verificationStatus::VERIFYING + ); +} + +(int, slice) committableView::success::committed(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + int nonce = NONCE; + cell $packet = MOCK_RECEIVE_PACKET(); + + cell $commitPOOO = $storage.cl::get(UlnConnection::commitPOOO); + $commitPOOO = POOO::set($commitPOOO, nonce); + + cell hashLookups = $storage.cl::get(UlnConnection::hashLookups); + + setContractStorage( + $storage + .UlnConnection::utils::deleteNonceAttestations(hashLookups, nonce) + .cl::set(UlnConnection::firstUnexecutedNonce, 1) + .cl::set(UlnConnection::commitPOOO, $commitPOOO) + ); + + return test::shouldBeTrue( + committableView(nonce, $packet, $defaultUlnReceiveConfig) == UlnConnection::verificationStatus::COMMITTED + ); +} + + +(int, slice) committableView::success::verifyingBranchTwo(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + int nonce = NONCE + 1234; + cell $packet = MOCK_RECEIVE_PACKET(); + + return test::shouldBeTrue(committableView(nonce, $packet, $defaultUlnReceiveConfig) == UlnConnection::verificationStatus::VERIFYING); +} + +(int, slice) committableView::success::executed(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + int nonce = NONCE; + cell $packet = MOCK_RECEIVE_PACKET(); + + ;; required dvns verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + return test::shouldBeTrue(committableView(nonce, $packet, $defaultUlnReceiveConfig) == UlnConnection::verificationStatus::COMMITTABLE); +} + +(int, slice) committableView::success::committedBranchTwo(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + int nonce = 2; + cell $packet = MOCK_RECEIVE_PACKET(); + + cell $commitPOOO = $storage.cl::get(UlnConnection::commitPOOO); + + $commitPOOO = POOO::set($commitPOOO, 1); + $commitPOOO = POOO::set($commitPOOO, 2); + $commitPOOO = POOO::set($commitPOOO, 3); + + setContractStorage( + $storage + .cl::set( + UlnConnection::commitPOOO, + $commitPOOO + ) + ); + + return test::shouldBeTrue(committableView(nonce, $packet, $defaultUlnReceiveConfig) == UlnConnection::verificationStatus::COMMITTED); +} + +(int, slice) verifiedView::success::basic(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + int nonce = NONCE; + cell $packet = MOCK_RECEIVE_PACKET(); + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + return test::shouldBeTrue( + verifiedView(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), nonce, $packet.cl::hash(), DEFAULT_CONFIRMATIONS) + & verifiedView(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1), nonce, $packet.cl::hash(), DEFAULT_CONFIRMATIONS) + ); +} + +(int, slice) verifiedView::success::notEnoughConfirmations(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + int addr = MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0); + + int nonce = NONCE; + cell $packet = MOCK_RECEIVE_PACKET(); + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS - 1), + $defaultUlnReceiveConfig, + addr + ) + ); + return test::shouldBeFalse(verifiedView(addr, nonce, $packet.cl::hash(), DEFAULT_CONFIRMATIONS)); +} + +(int, slice) verifiedView::success::wrongPacket(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + int nonce = NONCE; + int wrongPacketHash = 1234; + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + return test::shouldBeFalse( + verifiedView(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0), nonce, wrongPacketHash, DEFAULT_CONFIRMATIONS) + | verifiedView(MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1), nonce, wrongPacketHash, DEFAULT_CONFIRMATIONS) + ); +} + +(int, slice) verifiedView::success::wrongDvnAddress(cell $storage) impure { + + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + + int nonce = NONCE; + cell $packet = MOCK_RECEIVE_PACKET(); + + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + + return test::shouldBeFalse( + verifiedView(ARBITRARY_ADDRESS, nonce, $packet.cl::hash(), DEFAULT_CONFIRMATIONS) + ); +} + +(int, slice) msglibConnectionSyncChannelState::success::manualSetBits(cell $storage) inline { + int firstUnexecutedNonce = 1; + cell $channelNonceInfo = md::ChannelNonceInfo::New(5, firstUnexecutedNonce); + cell $commitPOOO = $storage.cl::get(UlnConnection::commitPOOO); + + $commitPOOO = POOO::set($commitPOOO, 1); + $commitPOOO = POOO::set($commitPOOO, 2); + $commitPOOO = POOO::set($commitPOOO, 3); + $commitPOOO = POOO::set($commitPOOO, 4); + + return test::handler::shouldPass( + msglibConnectionSyncChannelState, + md::MdObj::New($channelNonceInfo, getInitialStorage()), + emptyActions(), + $storage.cl::set( + UlnConnection::firstUnexecutedNonce, + firstUnexecutedNonce + ).cl::set(UlnConnection::commitPOOO, $commitPOOO), + txnContext + ); +} + +(int, slice) msglibConnectionSyncChannelState::success::maxSettableBit(cell $storage) inline { + int firstUnexecutedNonce = 1; + cell $channelNonceInfo = md::ChannelNonceInfo::New(1024, firstUnexecutedNonce); + cell $commitPOOO = POOO::New().cl::set(POOO::nextEmpty, 1024); + + return test::handler::shouldPass( + msglibConnectionSyncChannelState, + md::MdObj::New($channelNonceInfo, getInitialStorage()), + emptyActions(), + $storage.cl::set( + UlnConnection::firstUnexecutedNonce, + firstUnexecutedNonce + ) + .cl::set(UlnConnection::commitPOOO, $commitPOOO) + , + txnContext + ); +} + +(int, slice) msglibConnectionSyncChannelState::success::maxSettableBitTwo(cell $storage) inline { + int firstUnexecutedNonce = 1; + int firstUncommittedNonce = 2048; + cell $channelNonceInfo = md::ChannelNonceInfo::New(firstUncommittedNonce, firstUnexecutedNonce); + cell $commitPOOO = POOO::New().cl::set(POOO::nextEmpty, firstUncommittedNonce); + + return test::handler::shouldPass( + msglibConnectionSyncChannelState, + md::MdObj::New($channelNonceInfo, getInitialStorage()), + emptyActions(), + $storage.cl::set( + UlnConnection::firstUnexecutedNonce, + firstUnexecutedNonce + ).cl::set(UlnConnection::commitPOOO, $commitPOOO), + txnContext + ); +} + +(int, slice) msglibConnectionSyncChannelState::success::highBit(cell $storage) impure { + int firstUnexecutedNonce = 50; + int firstUncommittedNonce = 100; + cell $channelNonceInfo = md::ChannelNonceInfo::New(firstUncommittedNonce, firstUnexecutedNonce); + cell $commitPOOO = POOO::New(); + + int curNonce = 1; + while (curNonce < firstUncommittedNonce) { + $commitPOOO = POOO::set($commitPOOO, curNonce); + curNonce += 1; + } + + return test::handler::shouldPass( + msglibConnectionSyncChannelState, + md::MdObj::New($channelNonceInfo, getInitialStorage()), + emptyActions(), + $storage.cl::set( + UlnConnection::firstUnexecutedNonce, + firstUnexecutedNonce + ).cl::set(UlnConnection::commitPOOO, $commitPOOO), + txnContext + ); +} + +(int, slice) msglibConnectionSyncChannelState::success::gaps(cell $storage) impure { + cell $commitPOOO = POOO::New().POOO::set( 1).POOO::set(4); + $commitPOOO = POOO::set($commitPOOO, 2); + + cell $commitPOOO2 = POOO::New().POOO::set(1).POOO::set(4); + + int spoofFirstUncommittedNonce = 3; + throw_if(1, spoofFirstUncommittedNonce > POOO::maxSettableBit($commitPOOO2)); + + int nextEmpty = $commitPOOO2.cl::get(POOO::nextEmpty); + $commitPOOO2 = POOO::unsafeSetBits($commitPOOO2, nextEmpty, spoofFirstUncommittedNonce); + + return test::shouldBeTrue($commitPOOO.cl::hash() == $commitPOOO2.cl::hash()); +} + +(int, slice) ulnConnection::viewHashLookup::success::basic(cell $storage) impure { + cell $defaultUlnReceiveConfig = MOCK_DEFAULT_ULN_RECEIVE_CONFIG(); + ;; required dvns verify + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ) + ); + ulnConnectionVerify( + md::ExtendedMd::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + $defaultUlnReceiveConfig, + MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(1) + ) + ); + return test::shouldBeTrue( + viewHashLookup( + NONCE, MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(0) + ).cl::hash() == MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS).cl::get(md::UlnVerification::attestation).cl::hash() + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([_initialize::success::basic, "_initialize::success::basic"]) + ;; hashlookup utils + .tpush([UlnConnection::setHashLookup::success, "UlnConnection::setHashLookup::success"]) + ;; isCommittable utils + .tpush([UlnConnection::utils::getDVNsAndConf::fail, "UlnConnection::utils::getDVNsAndConf::fail"]) + ;; -- setOAppUlnSendConfig handler tests + .tpush([setOAppUlnSendConfig::success::basic, "setOAppUlnSendConfig::success::basic"]) + .tpush([setOAppUlnSendConfig::fail::nullAddress::dvnConfigError, "setOAppUlnSendConfig::fail::nullAddress::dvnConfigError"]) + .tpush([setOAppUlnSendConfig::fail::tooManyRefs::dvnConfigError, "setOAppUlnSendConfig::fail::tooManyRefs::dvnConfigError"]) + .tpush([setOAppUlnSendConfig::fail::maxDVNCount::dvnConfigError, "setOAppUlnSendConfig::fail::maxDVNCount::dvnConfigError"]) + .tpush([setOAppUlnSendConfig::success::malicious, "setOAppUlnSendConfig::success::malicious"]) + ;; -- setOAppUlnReceiveConfig handler tests + .tpush([setOAppUlnReceiveConfig::success::basic, "setOAppUlnReceiveConfig::success::basic"]) + .tpush([setOAppUlnReceiveConfig::fail::invalidMinCommitPacketGas, "setOAppUlnReceiveConfig::fail::invalidMinCommitPacketGas"]) + .tpush([setOAppUlnReceiveConfig::fail::optionalThresholdTooLarge, "setOAppUlnReceiveConfig::fail::optionalThresholdTooLarge"]) + .tpush([setOAppUlnReceiveConfig::fail::optionalThresholdTooLow, "setOAppUlnReceiveConfig::fail::optionalThresholdTooLow"]) + .tpush([setOAppUlnReceiveConfig::fail::dvnCountsAllNil, "setOAppUlnReceiveConfig::fail::dvnCountsAllNil"]) + .tpush([setOAppUlnReceiveConfig::success::malicious, "setOAppUlnReceiveConfig::success::malicious"]) + ;; -- msglibConnectionSend handler tests + .tpush([msglibConnectionSend::success::basic, "msglibConnectionSend::success::basic"]) + ;; -- ulnConnectionVerify handler tests + .tpush([ulnConnectionVerify::success::basic, "ulnConnectionVerify::success::basic"]) + .tpush([ulnConnectionVerify::success::minVerifiableNonce, "ulnConnectionVerify::success::minVerifiableNonce"]) + .tpush([ulnConnectionVerify::success::maxVerifiableNonce, "ulnConnectionVerify::success::maxVerifiableNonce"]) + .tpush([ulnConnectionVerify::success::twoNonces, "ulnConnectionVerify::success::twoNonces"]) + .tpush([ulnConnectionVerify::fail::nonceTooLarge, "ulnConnectionVerify::fail::nonceTooLarge"]) + .tpush([ulnConnectionVerify::fail::nonceTooSmall, "ulnConnectionVerify::fail::nonceTooSmall"]) + .tpush([ulnConnectionVerify::fail::wrongDVN, "ulnConnectionVerify::fail::wrongDVN"]) + .tpush([ulnConnectionVerify::revert::malformedAttestationStructure, "ulnConnectionVerify::revert::malformedAttestationStructure"]) + .tpush([ulnConnectionVerify::revert::malformedAttestationHeader, "ulnConnectionVerify::revert::malformedAttestationHeader"]) + .tpush([ulnConnectionVerify::success::partiallyMalformedAttestationHeader, "ulnConnectionVerify::success::partiallyMalformedAttestationHeader"]) + ;; -- ulnConnectionCommitPacket handler tests + .tpush([ulnConnectionCommitPacket::success::basic, "ulnConnectionCommitPacket::success::basic"]) + .tpush([ulnConnectionCommitPacket::success::withOptionalDVNs, "ulnConnectionCommitPacket::success::withOptionalDVNs"]) + .tpush([ulnConnectionCommitPacket::fail::wrongPacket, "ulnConnectionCommitPacket::fail::wrongPacket"]) + .tpush([ulnConnectionCommitPacket::fail::missingAllRequiredDVNs, "ulnConnectionCommitPacket::fail::missingAllRequiredDVNs"]) + .tpush([ulnConnectionCommitPacket::fail::missingSomeRequiredDVNs, "ulnConnectionCommitPacket::fail::missingSomeRequiredDVNs"]) + .tpush([ulnConnectionCommitPacket::fail::noAttestation, "ulnConnectionCommitPacket::fail::noAttestation"]) + .tpush([ulnConnectionCommitPacket::fail::notEnoughDefaultDVNConfirmations, "ulnConnectionCommitPacket::fail::notEnoughDefaultDVNConfirmations"]) + .tpush([ulnConnectionCommitPacket::fail::notEnoughOptionalDVNConfirmations, "ulnConnectionCommitPacket::fail::notEnoughOptionalDVNConfirmations"]) + .tpush([ulnConnectionCommitPacket::success::someOptionalDVNsHaveWrongPacketHash, "ulnConnectionCommitPacket::success::someOptionalDVNsHaveWrongPacketHash"]) + .tpush([ulnConnectionCommitPacket::fail::notEnoughCommitPacketGas, "ulnConnectionCommitPacket::fail::notEnoughCommitPacketGas"]) + .tpush([ulnConnectionCommitPacket::success::partiallyMalformedAttestationHeader, "ulnConnectionCommitPacket::success::partiallyMalformedAttestationHeader"]) + ;; -- msglibConnectionCommitPacketCallback handler tests + .tpush([msglibConnectionCommitPacketCallback::success::basic, "msglibConnectionCommitPacketCallback::success::basic"]) + .tpush([msglibConnectionCommitPacketCallback::success::notifyPacketExecuted, "msglibConnectionCommitPacketCallback::success::notifyPacketExecuted"]) + .tpush([msglibConnectionCommitPacketCallback::success::idempotent, "msglibConnectionCommitPacketCallback::success::idempotent"]) + ;; ;; -- garbageCollectInvalidAttestations handler tests + .tpush([garbageCollectInvalidAttestations::success::basic, "garbageCollectInvalidAttestations::success::basic"]) + .tpush([garbageCollectInvalidAttestations::success::maxDVNs, "garbageCollectInvalidAttestations::success::maxDVNs"]) + .tpush([garbageCollectInvalidAttestations::success::noEvictedDvns, "garbageCollectInvalidAttestations::success::noEvictedDvns"]) + .tpush([garbageCollectInvalidAttestations::success::noAttestation, "garbageCollectInvalidAttestations::success::noAttestation"]) + ;; -- garbageCollectExecutedNonces handler tests + .tpush([garbageCollectExecutedNonces::success::noExecutedNonce, "garbageCollectExecutedNonces::success::noExecutedNonce"]) + .tpush([garbageCollectExecutedNonces::success::noGarbageCollected, "garbageCollectExecutedNonces::success::noGarbageCollected"]) + .tpush([garbageCollectExecutedNonces::success::collectOne, "garbageCollectExecutedNonces::success::collectOne"]) + ;; -- msglibConnectionSyncChannelState handler tests + .tpush([msglibConnectionSyncChannelState::success::basic, "msglibConnectionSyncChannelState::success::basic"]) + .tpush([msglibConnectionSyncChannelState::success::manualSetBits, "msglibConnectionSyncChannelState::success::manualSetBits"]) + .tpush([msglibConnectionSyncChannelState::success::maxSettableBit, "msglibConnectionSyncChannelState::success::maxSettableBit"]) + .tpush([msglibConnectionSyncChannelState::success::highBit, "msglibConnectionSyncChannelState::success::highBit"]) + .tpush([msglibConnectionSyncChannelState::success::maxSettableBitTwo, "msglibConnectionSyncChannelState::success::maxSettableBitTwo"]) + .tpush([msglibConnectionSyncChannelState::success::gaps, "msglibConnectionSyncChannelState::success::gaps"]) + ;; -- msglibConnectionQuote handler tests + .tpush([msglibConnectionQuote::success::basic, "msglibConnectionQuote::success::basic"]) + ;; -- committableView view function tests + .tpush([committableView::success::basic, "committableView::success::basic"]) + .tpush([committableView::success::committed, "committableView::success::committed"]) + .tpush([committableView::success::verifyingBranchTwo, "committableView::success::verifyingBranchTwo"]) + .tpush([committableView::success::executed, "committableView::success::executed"]) + .tpush([committableView::success::committedBranchTwo, "committableView::success::committedBranchTwo"]) + ;; -- verifiedView view function tests + .tpush([verifiedView::success::basic, "verifiedView::success::basic"]) + .tpush([verifiedView::success::notEnoughConfirmations, "verifiedView::success::notEnoughConfirmations"]) + .tpush([verifiedView::success::wrongPacket, "verifiedView::success::wrongPacket"]) + .tpush([verifiedView::success::wrongDvnAddress, "verifiedView::success::wrongDvnAddress"]) + ;; -- viewHashLookup view function tests + .tpush([ulnConnection::viewHashLookup::success::basic, "ulnConnection::viewHashLookup::success::basic"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/permissions.fc new file mode 100644 index 00000000..8280828b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/permissions.fc @@ -0,0 +1,246 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../utils.fc"; + +#include "../../callbackOpcodes.fc"; +#include "../../msgdata/Attestation.fc"; +#include "../../msgdata/InitUlnConnection.fc"; +#include "../../msgdata/UlnReceiveConfig.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnVerification.fc"; + +#include "../../../interface.fc"; + +#include "../../../../interfaces.fc"; + +#include "../../../../../classes/lz/Path.fc"; +#include "../../../../../classes/msgdata/ChannelNonceInfo.fc"; +#include "../../../../../classes/msgdata/ExtendedMd.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../classes/msgdata/Nonce.fc"; +#include "../../../../../funC++/actions/call.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../funC++/stdlib.fc"; +#include "../../../../../funC++/utils.fc"; + +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; + +slice _testName() { return "UlnConnectionPermissions"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUlnConnection::New( + UlnSendConfig::NewWithDefaults(), + UlnReceiveConfig::NewWithDefaults(), + ENDPOINT_ADDRESS, + CHANNEL_ADDRESS + ) + ); +} + +cell createContractStorage() impure { + cell $path = lz::Path::New(SRC_EID, getContractAddress(), DST_EID, DST_OAPP); + setContractStorage( + UlnConnection::New(ULN_MANAGER_ADDRESS, $path, ULN_ADDRESS) + ); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) checkPermissions::msglibConnectionSend::success::basic(cell $storage) impure { + spoofCaller(CHANNEL_ADDRESS); + return test::permissions::shouldPass( + MsglibConnection::OP::MSGLIB_CONNECTION_SEND, + MOCK_LZ_SEND_WITH_ID(1) + ); +} + +(int, slice) checkPermissions::msglibConnectionSend::revert::notChannel(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + MsglibConnection::OP::MSGLIB_CONNECTION_SEND, + MOCK_LZ_SEND_WITH_ID(1) + ); +} + +(int, slice) checkPermissions::msglibConnectionCommitPacketCallback::success::basic(cell $storage) impure { + spoofCaller(CHANNEL_ADDRESS); + return test::permissions::shouldPass( + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New(NONCE, NONCE) + ); +} + +(int, slice) checkPermissions::msglibConnectionCommitPacketCallback::revert::notChannel(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldFail( + MsglibConnection::OP::MSGLIB_CONNECTION_COMMIT_PACKET_CALLBACK, + md::ChannelNonceInfo::New(NONCE, NONCE) + ); +} + +(int, slice) checkPermissions::ulnConnectionVerify::success::basic(cell $storage) impure { + spoofCaller(ULN_ADDRESS); + return test::permissions::shouldPass( + UlnConnection::OP::ULN_CONNECTION_VERIFY, + md::MdAddress::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + ULN_CONNECTION_ADDRESS + ) + ); +} + +(int, slice) checkPermissions::ulnConnectionVerify::revert::notUln(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnConnection::OP::ULN_CONNECTION_VERIFY, + md::MdAddress::New( + MOCK_ULN_VERIFICATION(DEFAULT_CONFIRMATIONS), + ULN_CONNECTION_ADDRESS + ) + ); +} + +(int, slice) checkPermissions::ulnConnectionCommitPacket::success::basic(cell $storage) impure { + spoofCaller(ULN_ADDRESS); + return test::permissions::shouldPass( + UlnConnection::OP::ULN_CONNECTION_COMMIT_PACKET, + md::MdObj::New( + MOCK_RECEIVE_PACKET(), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ) + ); +} + +(int, slice) checkPermissions::ulnConnectionCommitPacket::revert::onlyUln(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldFail( + UlnConnection::OP::ULN_CONNECTION_COMMIT_PACKET, + md::MdObj::New( + MOCK_RECEIVE_PACKET(), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ) + ); +} + +(int, slice) checkPermissions::msglibConnectionQuote::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + MsglibConnection::OP::MSGLIB_CONNECTION_QUOTE, + MOCK_LZ_SEND() + ); +} + +(int, slice) checkPermissions::GarbageCollectAttestations::success::basic(cell $storage) impure { + spoofCaller(ULN_ADDRESS); + return test::permissions::shouldPass( + UlnConnection::OP::GARBAGE_COLLECT_INVALID_ATTESTATIONS, + md::MdObj::New( + md::Nonce::New(NONCE), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ) + ); +} + +(int, slice) checkPermissions::GarbageCollectAttestations::revert::notUln(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnConnection::OP::GARBAGE_COLLECT_INVALID_ATTESTATIONS, + md::MdObj::New( + md::Nonce::New(NONCE), + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ) + ); +} + +(int, slice) checkPermissions::MsglibConnectionSyncChannelState::success::basic(cell $storage) impure { + spoofCaller(CHANNEL_ADDRESS); + return test::permissions::shouldPass( + MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE, + md::ChannelNonceInfo::New(NONCE, NONCE) + ); +} + +(int, slice) checkPermissions::MsglibConnectionSyncChannelState::revert::notChannel(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + MsglibConnection::OP::MSGLIB_CONNECTION_SYNC_CHANNEL_STATE, + md::ChannelNonceInfo::New(NONCE, NONCE) + ); +} + +(int, slice) checkPermissions::setOAppUlnSendConfig::success::basic(cell $storage) impure { + spoofCaller(ULN_MANAGER_ADDRESS); + return test::permissions::shouldPass( + UlnConnection::OP::SET_OAPP_ULN_SEND_CONFIG, + MOCK_CUSTOM_ULN_SEND_CONFIG() + ); +} + +(int, slice) checkPermissions::setOAppUlnSendConfig::revert::notUlnManager(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnConnection::OP::SET_OAPP_ULN_SEND_CONFIG, + MOCK_CUSTOM_ULN_SEND_CONFIG() + ); +} + +(int, slice) checkPermissions::setOAppUlnReceiveConfig::success::basic(cell $storage) impure { + spoofCaller(ULN_MANAGER_ADDRESS); + return test::permissions::shouldPass( + UlnConnection::OP::SET_OAPP_ULN_RECEIVE_CONFIG, + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ); +} + +(int, slice) checkPermissions::setOAppUlnReceiveConfig::revert::notUlnManager(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnConnection::OP::SET_OAPP_ULN_RECEIVE_CONFIG, + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ); +} + +(int, slice) checkPermissions::garbageCollectExecutedNoncess::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + UlnConnection::OP::GARBAGE_COLLECT_EXECUTED_NONCES, + cl::nullObject() + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([checkPermissions::msglibConnectionSend::success::basic, "checkPermissions::msglibConnectionSend::success::basic"]) + .tpush([checkPermissions::msglibConnectionSend::revert::notChannel, "checkPermissions::msglibConnectionSend::revert::notChannel"]) + .tpush([checkPermissions::msglibConnectionCommitPacketCallback::success::basic, "checkPermissions::msglibConnectionCommitPacketCallback::success::basic"]) + .tpush([checkPermissions::msglibConnectionCommitPacketCallback::revert::notChannel, "checkPermissions::msglibConnectionCommitPacketCallback::revert::notChannel"]) + .tpush([checkPermissions::ulnConnectionVerify::success::basic, "checkPermissions::ulnConnectionVerify::success::basic"]) + .tpush([checkPermissions::ulnConnectionVerify::revert::notUln, "checkPermissions::ulnConnectionVerify::revert::notUln"]) + .tpush([checkPermissions::ulnConnectionCommitPacket::success::basic, "checkPermissions::ulnConnectionCommitPacket::success::basic"]) + .tpush([checkPermissions::ulnConnectionCommitPacket::revert::onlyUln, "checkPermissions::ulnConnectionCommitPacket::revert::onlyUln"]) + .tpush([checkPermissions::msglibConnectionQuote::success::basic, "checkPermissions::msglibConnectionQuote::success::basic"]) + .tpush([checkPermissions::GarbageCollectAttestations::success::basic, "checkPermissions::GarbageCollectAttestations::success::basic"]) + .tpush([checkPermissions::GarbageCollectAttestations::revert::notUln, "checkPermissions::GarbageCollectAttestations::revert::notUln"]) + .tpush([checkPermissions::MsglibConnectionSyncChannelState::success::basic, "checkPermissions::MsglibConnectionSyncChannelState::success::basic"]) + .tpush([checkPermissions::MsglibConnectionSyncChannelState::revert::notChannel, "checkPermissions::MsglibConnectionSyncChannelState::revert::notChannel"]) + .tpush([checkPermissions::setOAppUlnSendConfig::success::basic, "checkPermissions::setOAppUlnSendConfig::success::basic"]) + .tpush([checkPermissions::setOAppUlnSendConfig::revert::notUlnManager, "checkPermissions::setOAppUlnSendConfig::revert::notUlnManager"]) + .tpush([checkPermissions::setOAppUlnReceiveConfig::success::basic, "checkPermissions::setOAppUlnReceiveConfig::success::basic"]) + .tpush([checkPermissions::setOAppUlnReceiveConfig::revert::notUlnManager, "checkPermissions::setOAppUlnReceiveConfig::revert::notUlnManager"]) + .tpush([checkPermissions::garbageCollectExecutedNoncess::success::basic, "checkPermissions::garbageCollectExecutedNoncess::success::basic"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/serde.fc new file mode 100644 index 00000000..651bf20d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/tests/serde.fc @@ -0,0 +1,211 @@ +#include "../../../../../../tests/baseSerdeTest.fc"; +#include "../../../../../../tests/mocks.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../../tests/consts.fc"; +#include "../../../../../funC++/dataStructures/PipelinedOutOfOrder.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "ULN Connection Serde"; } + +;;; ===============================TESTS========================================= + +;; UlnConnection: Has 9 getters, +;; Has 3 multi-getter (deserializer) +;; Has 2 setters +(int, slice) Serde::UlnConnection::getBaseStorage(cell $unused) impure { + cell $ulnConnectionStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ); + + return test::getRef::equal( + $ulnConnectionStorage, + UlnConnection::getBaseStorage, + UlnConnection::baseStorage + ); +} + +(int, slice) Serde::UlnConnection::getChannelAddress(cell $unused) impure { + cell $ulnConnectionStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ).cl::set(UlnConnection::channelAddress, CHANNEL_ADDRESS); + + return test::getData::equal( + $ulnConnectionStorage, + UlnConnection::getChannelAddress, + UlnConnection::channelAddress + ); +} + +(int, slice) Serde::UlnConnection::getUlnAddress(cell $unused) impure { + cell $ulnConnectionStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ); + + return test::getData::equal( + $ulnConnectionStorage, + UlnConnection::getUlnAddress, + UlnConnection::ulnAddress + ); +} + +(int, slice) Serde::UlnConnection::getCommitPOOO(cell $unused) impure { + cell $ulnConnectionStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ).cl::set(UlnConnection::commitPOOO, POOO::New().POOO::set(1)); + + return test::getRef::equal( + $ulnConnectionStorage, + UlnConnection::getCommitPOOO, + UlnConnection::commitPOOO + ); +} + +(int, slice) Serde::UlnConnection::getSendInformation(cell $unused) impure { + cell $ulnConnectionStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ) + .cl::set(UlnConnection::UlnSendConfigOApp, MOCK_CUSTOM_ULN_SEND_CONFIG()) + .cl::set(UlnConnection::channelAddress, CHANNEL_ADDRESS); + + ( + int ulnAddress, + int channelAddress, + cell sendConfig + ) = $ulnConnectionStorage.UlnConnection::getSendInformation(); + + return test::multiget::equal( + $ulnConnectionStorage, + unsafeTuple([ + UlnConnection::ulnAddress, + UlnConnection::channelAddress, + UlnConnection::UlnSendConfigOApp + ]), + unsafeTuple([ + ulnAddress, + channelAddress, + sendConfig + ]) + ); +} + +(int, slice) Serde::UlnConnection::getVerifyInformation(cell $unused) impure { + cell $ulnConnectionStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ).cl::set(UlnConnection::hashLookups, MOCK_SEND_PACKET()); + + ( + int firstUnexecutedNonce, + cell hashLookups, + cell receiveConfig + ) = $ulnConnectionStorage.UlnConnection::getVerifyInformation(); + + return test::multiget::equal( + $ulnConnectionStorage, + unsafeTuple([ + UlnConnection::firstUnexecutedNonce, + UlnConnection::hashLookups, + UlnConnection::UlnReceiveConfigOApp + ]), + unsafeTuple([ + firstUnexecutedNonce, + hashLookups, + receiveConfig + ]) + ); +} + +(int, slice) Serde::UlnConnection::getCommitPacketInformation(cell $unused) impure { + cell $ulnConnectionStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ).cl::set(UlnConnection::hashLookups, MOCK_SEND_PACKET()) + .cl::set(UlnConnection::UlnReceiveConfigOApp, MOCK_CUSTOM_ULN_SEND_CONFIG()); + + ( + int endpointAddress, + cell receiveConfig, + cell hashLookups + ) = $ulnConnectionStorage.UlnConnection::getCommitPacketInformation(); + + return test::multiget::equal( + $ulnConnectionStorage, + unsafeTuple([ + UlnConnection::endpointAddress, + UlnConnection::UlnReceiveConfigOApp, + UlnConnection::hashLookups + ]), + unsafeTuple([ + endpointAddress, + receiveConfig, + hashLookups + ]) + ); +} + +(int, slice) Serde::UlnConnection::setHashLookups(cell $unused) impure { + cell $ulnConnectionStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ); + + cell $hashLookups = cl::dict256::New() + .cl::dict256::setRef(NONCE, MOCK_ATTESTATION(DEFAULT_CONFIRMATIONS)); + + return test::set::equal( + $ulnConnectionStorage.cl::set(UlnConnection::hashLookups, $hashLookups), + $ulnConnectionStorage.UlnConnection::setHashLookups($hashLookups) + ); +} + +(int, slice) Serde::UlnConnection::updateCommittmentInfo(cell $unused) impure { + cell $ulnConnectionStorage = UlnConnection::New( + ULN_MANAGER_ADDRESS, + MOCK_SEND_PATH(), + ULN_ADDRESS + ); + + int committedNonce = 1; + int firstUnexecutedNonce = 1; + cell $commitPOOO = POOO::New().POOO::set(1); + + return test::build::equal( + $ulnConnectionStorage.UlnConnection::updateCommittmentInfo( + committedNonce, + firstUnexecutedNonce, + $commitPOOO + ), + $ulnConnectionStorage + .cl::set(UlnConnection::hashLookups, $ulnConnectionStorage.cl::get(UlnConnection::hashLookups).cl::dict256::delete(committedNonce)) + .cl::set(UlnConnection::commitPOOO, $commitPOOO) + .cl::set(UlnConnection::firstUnexecutedNonce, firstUnexecutedNonce) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::UlnConnection::getBaseStorage, "Serde::UlnConnection::getBaseStorage"]) + .tpush([Serde::UlnConnection::getChannelAddress, "Serde::UlnConnection::getChannelAddress"]) + .tpush([Serde::UlnConnection::getUlnAddress, "Serde::UlnConnection::getUlnAddress"]) + .tpush([Serde::UlnConnection::getCommitPOOO, "Serde::UlnConnection::getCommitPOOO"]) + .tpush([Serde::UlnConnection::getSendInformation, "Serde::UlnConnection::getSendInformation"]) + .tpush([Serde::UlnConnection::getVerifyInformation, "Serde::UlnConnection::getVerifyInformation"]) + .tpush([Serde::UlnConnection::getCommitPacketInformation, "Serde::UlnConnection::getCommitPacketInformation"]) + .tpush([Serde::UlnConnection::updateCommittmentInfo, "Serde::UlnConnection::updateCommittmentInfo"]) + .tpush([Serde::UlnConnection::setHashLookups, "Serde::UlnConnection::setHashLookups"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/utils.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/utils.fc new file mode 100644 index 00000000..946e5bed --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnConnection/utils.fc @@ -0,0 +1,52 @@ +#include "interface.fc"; +#include "storage.fc"; + +#include "../msgdata/Attestation.fc"; +#include "../msgdata/UlnReceiveConfig.fc"; +#include "../msgdata/UlnSendConfig.fc"; + +;;; ===============================GETTERS=========================== + +cell UlnConnection::utils::getHashLookup(cell hashLookups, int nonce, int dvnAddress) impure inline { + (cell addressLookup, int exists) = hashLookups.cl::dict256::get(nonce); + ifnot (exists) { + return cl::nullObject(); + } + + (cell $attestation, int exists) = addressLookup.cl::dict256::get(dvnAddress); + ifnot (exists) { + return cl::nullObject(); + } + + return $attestation; +} + +;;; ===============================SETTERS=========================== + +cell UlnConnection::utils::setHashLookup( + cell $self, + cell hashLookups, + int nonce, + int dvnAddress, + cell $attestation +) impure inline { + (cell addressLookup, int exists) = hashLookups.cl::dict256::get(nonce); + + ifnot (exists) { + addressLookup = cl::dict256::New(); + } + + ;; insert the attestation + addressLookup = addressLookup.cl::dict256::setRef(dvnAddress, $attestation); + + ;; save the attestation hash + return $self.UlnConnection::setHashLookups( + hashLookups.cl::dict256::setRef(nonce, addressLookup) + ); +} + +cell UlnConnection::utils::deleteNonceAttestations(cell $self, cell hashLookups, int nonce) impure inline { + ;; delete is safe to call on a non-existent key + hashLookups = hashLookups.cl::dict256::delete(nonce); + return $self.UlnConnection::setHashLookups(hashLookups); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/handler.fc new file mode 100644 index 00000000..0af305cf --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/handler.fc @@ -0,0 +1,529 @@ +#include "../../../core/abstract/protocolHandler.fc"; +#include "../../../interfaces.fc"; + +#include "../uln/interface.fc"; +#include "../ulnConnection/interface.fc"; + +#include "../../../channel/storage.fc"; +#include "../../../endpoint/storage.fc"; +#include "../../../endpoint/interface.fc"; + +#include "../../../../funC++/classlib.fc"; +#include "../../../../funC++/txnContext.fc"; +#include "../../../../funC++/utils.fc"; +#include "../../../../classes/lz/Path.fc"; +#include "../../../../classes/lz/MsglibInfo.fc"; +#include "../../../../classes/msgdata/CoinsAmount.fc"; +#include "../../../../classes/msgdata/Deploy.fc"; +#include "../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../classes/msgdata/AddMsglib.fc"; +#include "../../../../classes/msgdata/MdEid.fc"; +#include "../../../../classes/msgdata/MdObj.fc"; +#include "../../../../classes/msgdata/Bool.fc"; +#include "../../../../classes/msgdata/SetAddress.fc"; + +#include "../msgdata/UlnWorkerFeelibInfo.fc"; +#include "../msgdata/InitUln.fc"; +#include "../msgdata/InitUlnConnection.fc"; +#include "../msgdata/InitUlnManager.fc"; +#include "../msgdata/SetAdminWorkerAddresses.fc"; +#include "../msgdata/TreasuryFeeBps.fc"; +#include "../msgdata/UlnWorkerFeelibBytecode.fc"; + +#include "interface.fc"; +#include "storage.fc"; + +;;; ================INTERFACE FUNCTIONS===================== + +(cell, tuple) _initialize(cell $initUlnManager) impure inline { + (cell $storage, tuple actions) = preamble(); + + return ( + $storage + .cl::set( + UlnManager::endpointCode, + $initUlnManager.cl::get(md::InitUlnManager::endpointCode) + ) + .cl::set( + UlnManager::channelCode, + $initUlnManager.cl::get(md::InitUlnManager::channelCode) + ), + actions + ); +} + +int _getEventSink() impure inline { + return getContractAddress(); +} + +() _assertOAppPath(cell $mdObj) impure inline { + cell $sanitizedMdObj = $mdObj.md::MdObj::sanitize(); + cell $path = $sanitizedMdObj.cl::get(md::MdObj::obj); + cell $sanitizedPath = lz::Path::sanitize($path); + throw_unless( + UlnManager::ERROR::invalidPath, + getCaller() == $sanitizedPath.cl::get
(lz::Path::srcOApp) + ); +} + +() assertTentativeOwner() impure inline { + throw_unless( + UlnManager::ERROR::onlyTentativeOwner, + getCaller() == getContractStorage().cl::get
(UlnManager::tentativeOwner) + ); +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + if ( + (op == UlnManager::OP::DEPLOY_ULN) + | (op == UlnManager::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG) + | (op == UlnManager::OP::SET_DEFAULT_ULN_SEND_CONFIG) + | (op == UlnManager::OP::REGISTER_WORKER_FEELIB_BYTECODE) + | (op == UlnManager::OP::CLAIM_TREASURY_FEES) + | (op == UlnManager::OP::SET_ADMIN_WORKERS) + | (op == UlnManager::OP::SET_ULN_TREASURY_FEE_BPS) + | (op == UlnManager::OP::TRANSFER_OWNERSHIP) + ) { + ;; open to only owner + return assertOwner(); + } elseif ( + (op == MsglibManager::OP::GET_MSGLIB_INFO) + | (op == MsglibManager::OP::DEPLOY_CONNECTION) + | (op == UlnManager::OP::ADD_ULN_WORKER) + ) { + ;; open and public calls + return (); + } elseif ( + (op == MsglibManager::OP::SET_OAPP_MSGLIB_RECEIVE_CONFIG) + | (op == MsglibManager::OP::SET_OAPP_MSGLIB_SEND_CONFIG) + ) { + return _assertOAppPath($md); + } elseif (op == UlnManager::OP::CLAIM_OWNERSHIP) { + return assertTentativeOwner(); + } else { + ;; we must put a check for all opcodes to make sure we don't + ;; mistakenly miss an opcode's permissions + throw(BaseInterface::ERROR::invalidOpcode); + } +} + +;;; ==========================HELPER FUNCTIONS===================================== + +int _calculateUlnAddress(cell $storage, int dstEid) impure inline method_id { + throw_if(UlnManager::ERROR::invalidEid, dstEid == 0); + return computeContractAddress( + Uln::New( + getContractAddress(), + $storage.cl::get(UlnManager::eid), + dstEid + ), + $storage.cl::get(UlnManager::ulnCode) + ); +} + +int _calculateEndpointAddress(cell $storage, int dstEid) impure inline method_id { + throw_if(UlnManager::ERROR::invalidEid, dstEid == 0); + return computeContractAddress( + Endpoint::New( + $storage.cl::get(UlnManager::eid), + dstEid, + $storage.cl::get
(UlnManager::controllerAddress) + ), + $storage.cl::get(UlnManager::endpointCode) + ); +} + +int _calculateChannelAddress(cell $storage, cell $path) impure inline method_id { + return computeContractAddress( + Channel::New( + $storage.cl::get
(UlnManager::controllerAddress), + $path, + _calculateEndpointAddress($storage, $path.cl::get(lz::Path::dstEid)) + ), + $storage.cl::get(UlnManager::channelCode) + ); +} + +int _calculateUlnConnectionAddress(cell $storage, cell $path) impure inline method_id { + return computeContractAddress( + UlnConnection::New( + getContractAddress(), + $path, + _calculateUlnAddress($storage, $path.cl::get(lz::Path::dstEid)) + ), + $storage.cl::get(UlnManager::ulnConnectionCode) + ); +} + +;;; ==========================HANDLERS===================================== + +;; @in_opcode UlnManager::OP::DEPLOY_ULN +;; @in_md Deploy +;; @permissions only-owner +;; @out_actions deploy the uln for this particular eid +tuple deployUln(cell $deploy) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedDeploy = $deploy.md::Deploy::NewWithExtraInfo::sanitize(); + + int dstEid = $sanitizedDeploy.cl::get(md::Deploy::dstEid); + throw_if(UlnManager::ERROR::invalidEid, dstEid == 0); + + cell $initUln = $sanitizedDeploy + .cl::get(md::Deploy::extraInfo) + .md::InitUln::sanitize() + .cl::set( + md::InitUln::connectionCode, + $storage.cl::get(UlnManager::ulnConnectionCode) + ); + + actions~pushAction( + $storage.cl::get(UlnManager::ulnCode), + Uln::New( + getContractAddress(), + $storage.cl::get(UlnManager::eid), + dstEid + ), + $sanitizedDeploy.cl::get(md::Deploy::initialDeposit), + BaseInterface::OP::INITIALIZE, + $initUln, + 0 + ); + return actions; +} + +;; @in_opcode MsglibManager::OP::DEPLOY_CONNECTION +;; @in_md md::Deploy +;; @permissions seeded by caller address +;; @out_actions deploy the connection for this particular oApp path +tuple deployUlnConnection(cell $deploy) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedDeploy = $deploy.md::Deploy::NewWithExtraInfo::sanitize(); + + int dstEid = $sanitizedDeploy.cl::get(md::Deploy::dstEid); + + throw_if(UlnManager::ERROR::invalidEid, dstEid == 0); + + cell $path = lz::Path::New( + $storage.cl::get(UlnManager::eid), + getCaller(), + dstEid, + $sanitizedDeploy.cl::get
(md::Deploy::dstOApp) + ); + + cell $ulnConnection = UlnConnection::New( + getContractAddress(), + $path, + _calculateUlnAddress($storage, dstEid) + ); + + cell $initUlnConnection = $sanitizedDeploy + .cl::get(md::Deploy::extraInfo) + .md::InitUlnConnection::sanitize() + .cl::set(md::InitUlnConnection::endpointAddress, _calculateEndpointAddress($storage, dstEid)) + .cl::set(md::InitUlnConnection::channelAddress, _calculateChannelAddress($storage, $path)); + + actions~pushAction( + $storage.cl::get(UlnManager::ulnConnectionCode), + $ulnConnection, + $sanitizedDeploy.cl::get(md::Deploy::initialDeposit), + BaseInterface::OP::INITIALIZE, + $initUlnConnection, + 0 + ); + + return actions; +} + +tuple getMsglibInfo(cell $addMsglib) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedAddMsglib = $addMsglib.md::AddMsglib::sanitize(); + + int dstEid = $sanitizedAddMsglib.cl::get(md::AddMsglib::dstEid); + throw_if(UlnManager::ERROR::invalidEid, dstEid == 0); + + int ulnAddress = _calculateUlnAddress( + $storage, + dstEid + ); + + actions~pushAction( + getCaller(), + Endpoint::OP::GET_MSGLIB_INFO_CALLBACK, + lz::MsglibInfo::New( + ulnAddress, + $storage.cl::get(UlnManager::ulnConnectionCode), + UlnConnection::New( + getContractAddress(), + lz::Path::endpointPath( + $storage.cl::get(UlnManager::eid), + dstEid + ), + ulnAddress + ) + ) + ); + + return actions; +} + +;; @in_opcode UlnManager::OP::SET_DEFAULT_ULN_SEND_CONFIG +tuple setDefaultUlnSendConfig(cell $mdEid) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedMdEid = $mdEid.md::MdEid::sanitize(); + + cell $UlnSendConfig = $sanitizedMdEid.cl::get(md::MdEid::md); + + actions~pushAction( + _calculateUlnAddress($storage, $sanitizedMdEid.cl::get(md::MdEid::eid)), + Uln::OP::SET_DEFAULT_ULN_SEND_CONFIG, + $UlnSendConfig + ); + + return actions; +} + +;; @in_opcode UlnManager::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG +tuple setDefaultUlnReceiveConfig(cell $mdEid) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedMdEid = $mdEid.md::MdEid::sanitize(); + + cell $UlnReceiveConfig = $sanitizedMdEid.cl::get(md::MdEid::md); + + actions~pushAction( + _calculateUlnAddress($storage, $sanitizedMdEid.cl::get(md::MdEid::eid)), + Uln::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG, + $UlnReceiveConfig + ); + + return actions; +} + +;; @in_opcode UlnManager::OP::SET_CONNECTION_ULN_CONFIG +tuple setOAppUlnReceiveConfig(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedMdObj = $mdObj.md::MdObj::sanitize(); + + cell $sendPath = $sanitizedMdObj + .cl::get(md::MdObj::obj) + .lz::Path::sanitize(); + + actions~pushAction( + _calculateUlnConnectionAddress($storage, $sendPath), + UlnConnection::OP::SET_OAPP_ULN_RECEIVE_CONFIG, + $sanitizedMdObj.cl::get(md::MdObj::md) + ); + + return actions; +} + +;; @in_opcode UlnManager::OP::SET_CONNECTION_ULN_CONFIG +tuple setOAppUlnSendConfig(cell $mdObj) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedMdObj = $mdObj.md::MdObj::sanitize(); + + cell $sendPath = $sanitizedMdObj + .cl::get(md::MdObj::obj) + .lz::Path::sanitize(); + + actions~pushAction( + _calculateUlnConnectionAddress($storage, $sendPath), + UlnConnection::OP::SET_OAPP_ULN_SEND_CONFIG, + $sanitizedMdObj.cl::get(md::MdObj::md) + ); + + return actions; +} + +;; @in_opcode UlnManager::OP::REGISTER_WORKER_FEELIB_BYTECODE +tuple registerWorkerFeelibBytecode(cell $ulnWorkerFeelibBytecode) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + cell $sanitizedUlnWorkerFeelibBytecode = $ulnWorkerFeelibBytecode + .md::UlnWorkerFeelibBytecode::sanitize(); + + cell bytecode = $sanitizedUlnWorkerFeelibBytecode + .cl::get(md::UlnWorkerFeelibBytecode::bytecode); + + cell $newStorage = $storage + .cl::nestedDict256::setRef( + UlnManager::workerFeelibBytecodes, + bytecode.cl::hash(), + bytecode + ); + + (_, _, _, int success) = compute_data_size?( + $newStorage.cl::get(UlnManager::workerFeelibBytecodes), + UlnManager::CONST::MAX_CUMULATIVE_BYTECODE_CELLS + ); + + throw_unless(UlnManager::ERROR::feelibBytecodesExceeded, success); + + setContractStorage($newStorage); + + return actions; +} + +tuple addWorkerFeelibToUln(cell $UlnWorkerFeelibInfo) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + + ;; invariant: guarantees that getCaller() == $UlnWorkerFeelibInfo.workerAddress + cell $sanitizedUlnWorkerFeelibInfo = $UlnWorkerFeelibInfo + .UlnWorkerFeelibInfo::sanitize() + .cl::set(UlnWorkerFeelibInfo::workerAddress, getCaller()); + + int bytecodeId = $sanitizedUlnWorkerFeelibInfo + .cl::get(UlnWorkerFeelibInfo::workerFeelibBytecode) + .cell_hash(); + + (_, int bytecodeExists) = $storage + .cl::nestedDict256::get(UlnManager::workerFeelibBytecodes, bytecodeId); + + throw_unless(UlnManager::ERROR::invalidWorkerInfo, bytecodeExists); + + ;; only admin workers can add to the admin workers list on the uln + int useAdminSlot = $sanitizedUlnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::isAdmin); + if (useAdminSlot) { + throw_unless( + UlnManager::ERROR::invalidWorkerInfo, + AddressList::includes( + getCaller(), + $storage.cl::get(UlnManager::adminWorkers).begin_parse() + ) + ); + } + + actions~pushAction( + _calculateUlnAddress( + $storage, + $sanitizedUlnWorkerFeelibInfo.cl::get(UlnWorkerFeelibInfo::dstEid) + ), + Uln::OP::UPDATE_WORKER_FEELIB, + $sanitizedUlnWorkerFeelibInfo + ); + + return actions; +} + +tuple setAdminWorkers(cell $setAdminWorkerAddresses) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedSetAdminWorkerAddresses = $setAdminWorkerAddresses.md::SetAdminWorkerAddresses::sanitize(); + + cell adminWorkers = $sanitizedSetAdminWorkerAddresses + .cl::get(md::SetAdminWorkerAddresses::adminWorkers); + + throw_unless( + UlnManager::ERROR::invalidAdminWorkerList, + AddressList::isValid(adminWorkers, UlnManager::CONST::MAX_ADMIN_WORKERS) + ); + + setContractStorage( + $storage.cl::set(UlnManager::adminWorkers, adminWorkers) + ); + + actions~pushAction( + UlnManager::event::ADMIN_WORKERS_SET, + $sanitizedSetAdminWorkerAddresses + ); + + return actions; +} + +tuple claimTreasuryFees(cell $coinsAmount) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedCoinsAmount = $coinsAmount.md::CoinsAmount::sanitize(); + + int claimAmount = $sanitizedCoinsAmount.cl::get(md::CoinsAmount::amount); + (int myStateCellCount, int myStateBitCount) = getContractStateSize( + my_code(), + getContractStorage() + ); + + int minimumStorageFee = get_storage_fee( + BASECHAIN, + UlnManager::CONST::minRentSeconds, + myStateBitCount, + myStateCellCount + ); + + ;; Ensure the owner cannot rug the contract by pulling out all the rent + throw_if( + UlnManager::ERROR::insufficientBalance, + (getInitialContractBalance() - storage_fees() - claimAmount) < minimumStorageFee + ); + + actions~pushAction(getOrigin(), claimAmount, claimAmount); + + return actions; +} + +tuple setUlnTreasuryFeeBps(cell $mdEid) impure inline method_id { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedMdEid = $mdEid.md::MdEid::sanitize(); + + int ulnAddress = _calculateUlnAddress($storage, $sanitizedMdEid.cl::get(md::MdEid::eid)); + cell $treasuryFeeBps = $sanitizedMdEid.cl::get(md::MdEid::md); + actions~pushAction( + ulnAddress, + Uln::OP::SET_TREASURY_FEE_BPS, + $treasuryFeeBps + ); + return actions; +} + +;; This will override an existing tentative owner +;; Becuase we assert that the 'claimOwnership' can't be done by the 'null' address, it means +;; if we want to burn the ownership, we will need to set it to a contract which only has ability to +;; claim, but nothing else. This effectively burns it. +tuple transferOwnership(cell $setAddress) impure inline { + (cell $storage, tuple actions) = preamble(); + cell $sanitizedSetAddress = $setAddress.md::SetAddress::sanitize(); + + setContractStorage( + $storage.cl::set( + UlnManager::tentativeOwner, + $sanitizedSetAddress.cl::get
(md::SetAddress::address) + ) + ); + + actions~pushAction( + UlnManager::event::OWNER_SET_TENTATIVE, + $sanitizedSetAddress + ); + + return actions; +} + +tuple claimOwnership(cell $emptyMd) impure inline { + (cell $storage, tuple actions) = preamble(); + + ;; permissions check guarantees newOwner == $storage.tentativeOwner + int newOwner = $storage.cl::get
(UlnManager::tentativeOwner); + + ;; this should be caught by the permission check, BUT let's be super safe about this + throw_if(UlnManager::ERROR::nullTentativeOwner, newOwner == NULLADDRESS); + + ;; erase the tentative owner AND set that tentative owner to the actual owner + setContractStorage( + $storage + .cl::set(UlnManager::tentativeOwner, NULLADDRESS) + .cl::set( + UlnManager::baseStorage, + $storage + .cl::get(UlnManager::baseStorage) + .cl::set(BaseStorage::owner, newOwner) + ) + ); + + actions~pushAction( + UlnManager::event::OWNER_SET, + md::SetAddress::New(newOwner) + ); + + return actions; +} + +(int, int, int) version() impure method_id { + return (3, 0, 2); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/interface.fc new file mode 100644 index 00000000..3a83e33a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/interface.fc @@ -0,0 +1,53 @@ +#include "storage.fc"; + +;; Deploy a new ULN (eid-shard) +;; only-owner +const int UlnManager::OP::DEPLOY_ULN = "UlnManager::OP::DEPLOY_ULN"c; +;; Deploy a new connection (oApp path shard) +;; called by OApp, seeded by SENDER +const int MsglibManager::OP::DEPLOY_CONNECTION = "MsglibManager::OP::DEPLOY_CONNECTION"c; +;; Set the default ULN config +;; only-owner +const int UlnManager::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG = "UlnManager::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG"c; + +const int UlnManager::OP::SET_DEFAULT_ULN_SEND_CONFIG = "UlnManager::OP::SET_DEFAULT_ULN_SEND_CONFIG"c; + +;; Note: this is called withdraw treasury fees but really it's just withdraw anything you want +const int UlnManager::OP::CLAIM_TREASURY_FEES = "UlnManager::OP::CLAIM_TREASURY_FEES"c; + +;; Register a new approved bytecode +;; only-owner +const int UlnManager::OP::REGISTER_WORKER_FEELIB_BYTECODE = "UlnManager::OP::REGISTER_WORKER_FEELIB_INFO"c; + +;; Add a new worker to the ULN +;; only-owner +const int UlnManager::OP::ADD_ULN_WORKER = "UlnManager::OP::ADD_ULN_WORKER"c; + +;; Set/Remove an admin worker +;; only-owner +const int UlnManager::OP::SET_ADMIN_WORKERS = "UlnManager::OP::SET_ADMIN_WORKERS"c; +const int UlnManager::OP::SET_ULN_TREASURY_FEE_BPS = "UlnManager::OP::SET_ULN_TREASURY_FEE_BPS"c; + +const int UlnManager::OP::TRANSFER_OWNERSHIP = "UlnManager::OP::TRANSFER_OWNERSHIP"c; +const int UlnManager::OP::CLAIM_OWNERSHIP = "UlnManager::OP::CLAIM_OWNERSHIP"c; + +const int UlnManager::ERROR::insufficientBalance = 161; +const int UlnManager::ERROR::invalidEid = 162; +const int UlnManager::ERROR::invalidWorkerInfo = 163; +const int UlnManager::ERROR::workerIsNotAdmin = 164; +const int UlnManager::ERROR::feelibBytecodesExceeded = 166; +const int UlnManager::ERROR::invalidPath = 167; +const int UlnManager::ERROR::invalidAdminWorkerList = 168; +const int UlnManager::ERROR::onlyTentativeOwner = 169; +const int UlnManager::ERROR::nullTentativeOwner = 170; + +const int UlnManager::event::ADMIN_WORKERS_SET = "UlnMgr::event::ADMIN_WORKERS_SET"u; +const int UlnManager::event::OWNER_SET_TENTATIVE = "UlnMgr::event::OWNER_SET_TNTV"u; +const int UlnManager::event::OWNER_SET = "UlnMgr::event::OWNER_SET"u; + +;; each additional cell seems to add about 110 gas +;; so 7000 cells is about 770,000 gas. +;; Workers based on the existing base worker each cost about 20-30 cells +;; so 7000 cells is about 350-500 workers +const int UlnManager::CONST::MAX_CUMULATIVE_BYTECODE_CELLS = 7000; +const int UlnManager::CONST::MAX_ADMIN_WORKERS = 256; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/main.fc new file mode 100644 index 00000000..2de3812a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/main.fc @@ -0,0 +1,40 @@ +#include "handler.fc"; +#include "interface.fc"; + +#include "../../interface.fc"; ;; msglib interface + +#include "../../../core/abstract/protocolMain.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == UlnManager::OP::DEPLOY_ULN) { + return deployUln($md); + } elseif (op == MsglibManager::OP::DEPLOY_CONNECTION) { + return deployUlnConnection($md); + } elseif (op == MsglibManager::OP::GET_MSGLIB_INFO) { + return getMsglibInfo($md); + } elseif (op == UlnManager::OP::SET_DEFAULT_ULN_SEND_CONFIG) { + return setDefaultUlnSendConfig($md); + } elseif (op == UlnManager::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG) { + return setDefaultUlnReceiveConfig($md); + } elseif (op == MsglibManager::OP::SET_OAPP_MSGLIB_RECEIVE_CONFIG) { + return setOAppUlnReceiveConfig($md); + } elseif (op == MsglibManager::OP::SET_OAPP_MSGLIB_SEND_CONFIG) { + return setOAppUlnSendConfig($md); + } elseif (op == UlnManager::OP::CLAIM_TREASURY_FEES) { + return claimTreasuryFees($md); + } elseif (op == UlnManager::OP::ADD_ULN_WORKER) { + return addWorkerFeelibToUln($md); + } elseif (op == UlnManager::OP::REGISTER_WORKER_FEELIB_BYTECODE) { + return registerWorkerFeelibBytecode($md); + } elseif (op == UlnManager::OP::SET_ADMIN_WORKERS) { + return setAdminWorkers($md); + } elseif (op == UlnManager::OP::SET_ULN_TREASURY_FEE_BPS) { + return setUlnTreasuryFeeBps($md); + } elseif (op == UlnManager::OP::TRANSFER_OWNERSHIP) { + return transferOwnership($md); + } elseif (op == UlnManager::OP::CLAIM_OWNERSHIP) { + return claimOwnership($md); + } + throw(BaseInterface::ERROR::invalidOpcode); + return null(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/storage.fc new file mode 100644 index 00000000..4481f331 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/storage.fc @@ -0,0 +1,44 @@ +#include "../../../core/baseStorage.fc"; + +;; 3 years +const int UlnManager::CONST::minRentSeconds = 3600 * 24 * 365 * 3; + +const int UlnManager::NAME = "ulnMgr"u; + +const int UlnManager::baseStorage = 0; + +;; Bytecodes +const int UlnManager::ulnCode = 1; +const int UlnManager::ulnConnectionCode = 2; +const int UlnManager::controllerAddress = 3; +const int UlnManager::eid = 4; +const int UlnManager::endpointCode = 5; +const int UlnManager::channelCode = 6; +const int UlnManager::workerFeelibBytecodes = 7; +const int UlnManager::adminWorkers = 8; +const int UlnManager::tentativeOwner = 9; + +;; @owner LayerZero admin EOA +cell UlnManager::New( + int owner, + cell ulnCode, + cell connectionCode, + int controllerAddress, + int eid +) impure inline method_id { + return cl::declare( + UlnManager::NAME, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(owner)], ;; UlnManager::baseStorage + [cl::t::cellRef, ulnCode], ;; UlnManager::ulnCode + [cl::t::cellRef, connectionCode], ;; UlnManager::ulnConnectionCode + [cl::t::address, controllerAddress], ;; UlnManager::controllerAddress + [cl::t::uint32, eid], ;; UlnManager::eid + [cl::t::cellRef, empty_cell()], ;; UlnManager::endpointCode + [cl::t::cellRef, empty_cell()], ;; UlnManager::channelCode + [cl::t::dict256, cl::dict256::New()], ;; UlnManager::workerBytecodes + [cl::t::addressList, empty_cell()], ;; UlnManager::adminWorkers + [cl::t::address, NULLADDRESS] ;; UlnManager::tentativeOwner + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/main.fc new file mode 100644 index 00000000..60b72693 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/main.fc @@ -0,0 +1,664 @@ +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/mocks.fc"; + +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../msgdata/InitUln.fc"; +#include "../../msgdata/InitUlnManager.fc"; +#include "../../msgdata/SetAdminWorkerAddresses.fc"; +#include "../../msgdata/TreasuryFeeBps.fc"; +#include "../../msgdata/UlnReceiveConfig.fc"; +#include "../../msgdata/UlnSendConfig.fc"; +#include "../../msgdata/UlnWorkerFeelibInfo.fc"; +#include "../../uln/interface.fc"; +#include "../../ulnConnection/interface.fc"; + +#include "../../../../../classes/msgdata/Deploy.fc"; + +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/baseInterface.fc"; +#include "../../../../../funC++/testutils.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../funC++/txnContext.fc"; +#include "../../../../../classes/msgdata/MdEid.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../classes/msgdata/SetAddress.fc"; +#include "../../../../../funC++/actions/call.fc"; +#include "../../../../../funC++/actions/deploy.fc"; +#include "../../../../../funC++/actions/event.fc"; +#include "../../../../../funC++/dataStructures/AddressList.fc"; + +slice _testName() { return "ulnManager"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUlnManager::New( + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE() + ) + ); +} + +cell createContractStorage() impure { + setContractStorage( + UlnManager::New( + getCaller(), + MOCK_ULN_CODE(), + MOCK_ULN_CONNECTION_CODE(), + CONTROLLER_ADDRESS, + SRC_EID + ) + ); + return getContractStorage(); +} + +;; ============================== deploy uln tests ============================== + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +(int, slice) initialize::success::basic(cell $storage) impure { + cell $initializedStorage = getContractStorage(); + createContractStorage(); + authenticate(); + return test::handler::shouldPass( + initialize, + md::InitUlnManager::New( + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE() + ), + emptyActions(), + $initializedStorage, + txnContext + ); +} + +(int, slice) deployUln::success::basic(cell $storage) impure { + cell $initUlnMd = md::InitUln::New( + MOCK_ULN_CONNECTION_CODE(), + TREASURY_FEE_BPS + ); + + return test::handler::shouldPass( + deployUln, + md::Deploy::NewWithExtraInfo( + INITIAL_DEPOSIT, + DST_EID, + NULLADDRESS, + $initUlnMd + ), + unsafeTuple([ + 0, + _newAction( + MOCK_ULN_CODE(), + Uln::New(getContractAddress(), SRC_EID, DST_EID), + INITIAL_DEPOSIT, + BaseInterface::OP::INITIALIZE, + $initUlnMd, + 0 + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) deployUln::fail::invalidEid(cell $storage) impure { + return test::handler::shouldFail( + deployUln, + md::Deploy::New(INITIAL_DEPOSIT, 0, NULLADDRESS), + UlnManager::ERROR::invalidEid + ); +} + +;; ============================== deploy uln connection tests ============================== + +(int, slice) deployUlnConnection::success::basic(cell $storage) impure { + spoofCaller(SRC_OAPP); + cell $storage = getContractStorage(); + + cell $path = lz::Path::New(SRC_EID, SRC_OAPP, DST_EID, DST_OAPP); + + cell $initUlnConnection = md::InitUlnConnection::NewOnlyConfig( + UlnSendConfig::NewWithDefaults(), + UlnReceiveConfig::NewWithDefaults() + ); + + cell $deploy = md::Deploy::New(INITIAL_DEPOSIT, DST_EID, DST_OAPP) + .cl::set(md::Deploy::extraInfo, $initUlnConnection); + + return test::handler::shouldPass( + deployUlnConnection, + $deploy, + unsafeTuple([ + 0, + _newAction( + MOCK_ULN_CONNECTION_CODE(), + UlnConnection::New( + getContractAddress(), + $path, + _calculateUlnAddress(getContractStorage(), DST_EID) + ), + INITIAL_DEPOSIT, + BaseInterface::OP::INITIALIZE, + md::InitUlnConnection::New( + UlnSendConfig::NewWithDefaults(), + UlnReceiveConfig::NewWithDefaults(), + _calculateEndpointAddress($storage, DST_EID), + _calculateChannelAddress($storage, $path) + ), + 0 + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) deployUlnConnection::fail::invalidEid(cell $storage) impure { + return test::handler::shouldFail( + deployUlnConnection, + md::Deploy::New(INITIAL_DEPOSIT, 0, NULLADDRESS), + UlnManager::ERROR::invalidEid + ); +} + +;; ============================== getMsglibInfo tests ============================== + +(int, slice) getMsglibInfo::success::basic(cell $storage) impure { + int ulnAddress = _calculateUlnAddress(getContractStorage(), DST_EID); + + return test::handler::shouldPass( + getMsglibInfo, + md::AddMsglib::New(NULLADDRESS, DST_EID), + unsafeTuple([ + 0, + _newAction( + getCaller(), + Endpoint::OP::GET_MSGLIB_INFO_CALLBACK, + lz::MsglibInfo::New( + ulnAddress, + MOCK_ULN_CONNECTION_CODE(), + UlnConnection::New( + getContractAddress(), + lz::Path::endpointPath(SRC_EID, DST_EID), + ulnAddress + ) + ) + ) + ]), + getContractStorage(), + txnContext + ); + } + +(int, slice) getMsglibInfo::fail::invalidEid(cell $storage) impure { + return test::handler::shouldFail( + getMsglibInfo, + md::AddMsglib::New(NULLADDRESS, 0), + UlnManager::ERROR::invalidEid + ); +} + +;; ============================== setDefaultUlnReceiveConfig tests ============================== + +(int, slice) setDefaultUlnReceiveConfig::success::basic(cell $storage) impure { + return test::handler::shouldPass( + setDefaultUlnReceiveConfig, + md::MdEid::New(MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), DST_EID), + unsafeTuple([ + 0, + _newAction( + _calculateUlnAddress(getContractStorage(), DST_EID), + Uln::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG, + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ) + ]), + getContractStorage(), + txnContext + ); +} + +;; ============================== setOAppUlnReceiveConfig tests ============================== + +(int, slice) setOAppUlnReceiveConfig::success::basic(cell $storage) impure { + return test::handler::shouldPass( + setOAppUlnReceiveConfig, + md::MdObj::New(MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), MOCK_SEND_PATH()), + unsafeTuple([ + 0, + _newAction( + _calculateUlnConnectionAddress(getContractStorage(), MOCK_SEND_PATH()), + UlnConnection::OP::SET_OAPP_ULN_RECEIVE_CONFIG, + MOCK_DEFAULT_ULN_RECEIVE_CONFIG() + ) + ]), + getContractStorage(), + txnContext + ); +} +;; ============================== claimTreasuryFees tests ============================== + +(int, slice) claimTreasuryFees::success::basic(cell $storage) impure { + (int myStateCellCount, int myStateBitCount) = getContractStateSize( + my_code(), + getContractStorage() + ); + + int minStorageFee = get_storage_fee( + BASECHAIN, + UlnManager::CONST::minRentSeconds, + myStateBitCount, + myStateCellCount + ); + + ;; the code needs you to have enough balance (minus the incoming msg value) to pay the claim amount + ;; (the contract balance before this is actually bigger, but we want to be able to reason about it) + setContractBalance( + getMsgValue() + MOCK_ULN_TREASURY_CLAIM_AMOUNT + minStorageFee + storage_fees() + ); + + return test::handler::shouldPass( + claimTreasuryFees, + md::CoinsAmount::New(MOCK_ULN_TREASURY_CLAIM_AMOUNT), + unsafeTuple([ + MOCK_ULN_TREASURY_CLAIM_AMOUNT, + _newAction( + getOrigin(), + MOCK_ULN_TREASURY_CLAIM_AMOUNT, + MOCK_ULN_TREASURY_CLAIM_AMOUNT + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) claimTreasuryFees::fail::insufficientBalance(cell $storage) impure { + setContractBalance(getMsgValue()); + return test::handler::shouldFail( + claimTreasuryFees, + md::CoinsAmount::New(MOCK_ULN_TREASURY_CLAIM_AMOUNT), + UlnManager::ERROR::insufficientBalance + ); +} + +;; ============================== setAdminWorker tests ============================== + +(int, slice) setAdminWorker::success::addAdminWorker(cell $storage) impure { + cell $md = md::SetAdminWorkerAddresses::New( + MOCK_ADMIN_WORKER_LIST() + ); + + return test::handler::shouldPass( + setAdminWorkers, + $md, + unsafeTuple([ + 0, + _newAction( + UlnManager::event::ADMIN_WORKERS_SET, + $md + ) + ]), + getContractStorage() + .cl::set(UlnManager::adminWorkers, MOCK_ADMIN_WORKER_LIST()), + txnContext + ); +} + +(int, slice) setAdminWorker::revert::tooManyAdmins(cell $storage) impure { + cell addressListNode = AddressList::serialize( + unsafeTuple( + [ + ADMIN_WORKER_ADDRESS, + ADMIN_WORKER_ADDRESS, + ADMIN_WORKER_ADDRESS + ] + ) + ); + + repeat (UlnManager::CONST::MAX_ADMIN_WORKERS / 3) { + addressListNode = begin_cell() + .store_uint256(ADMIN_WORKER_ADDRESS) + .store_uint256(ADMIN_WORKER_ADDRESS) + .store_uint256(ADMIN_WORKER_ADDRESS) + .store_ref(addressListNode) + .end_cell(); + } + + + return test::handler::shouldFail( + setAdminWorkers, + md::SetAdminWorkerAddresses::New(addressListNode), + UlnManager::ERROR::invalidAdminWorkerList + ); +} + +(int, slice) setAdminWorker::revert::malformedAdminList(cell $storage) impure { + cell addressListNode = AddressList::serialize( + unsafeTuple( + [ + ADMIN_WORKER_ADDRESS, + ADMIN_WORKER_ADDRESS, + ADMIN_WORKER_ADDRESS + ] + ) + ); + + return test::handler::shouldFail( + setAdminWorkers, + md::SetAdminWorkerAddresses::New( + _dupWithGarbage(addressListNode) + ), + UlnManager::ERROR::invalidAdminWorkerList + ); +} + +;; ============================== addWorkerFeelibToUlnFeelibInfo tests ============================== + +(int, slice) registerWorkerFeelibBytecode::success::basic(cell $storage) impure { + return test::handler::shouldPass( + registerWorkerFeelibBytecode, + MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD(), + emptyActions(), + getContractStorage() + .cl::nestedDict256::setRef( + UlnManager::workerFeelibBytecodes, + MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE().cell_hash(), + MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE() + ), + txnContext + ); +} + +(int, slice) registerWorkerFeelibBytecode::success::alreadyRegistered(cell $storage) impure { + registerWorkerFeelibBytecode(MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD()); + + return test::handler::shouldPass( + registerWorkerFeelibBytecode, + MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD(), + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) addWorkerFeelibToUln::success::usePermissionlessSlot(cell $storage) impure { + cell $UlnWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO_WITH_STORAGE(); + registerWorkerFeelibBytecode(MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD()); + + return test::handler::shouldPass( + addWorkerFeelibToUln, + $UlnWorkerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + _calculateUlnAddress(getContractStorage(), DST_EID), + Uln::OP::UPDATE_WORKER_FEELIB, + $UlnWorkerFeelibInfo + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) addWorkerFeelibToUln::success::useAdminSlot(cell $storage) impure { + ;; add the caller as an admin worker + setAdminWorkers( + md::SetAdminWorkerAddresses::New( + AddressList::serialize(unsafeTuple([getCaller()])) + ) + ); + + cell $UlnWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO_WITH_STORAGE(); + + registerWorkerFeelibBytecode(MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD()); + + return test::handler::shouldPass( + addWorkerFeelibToUln, + $UlnWorkerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + _calculateUlnAddress(getContractStorage(), DST_EID), + Uln::OP::UPDATE_WORKER_FEELIB, + $UlnWorkerFeelibInfo + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) addWorkerFeelibToUln::fail::useAdminSlotAsNonAdmin(cell $storage) impure { + cell $UlnWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO_WITH_STORAGE(); + + return test::handler::shouldFail( + addWorkerFeelibToUln, + $UlnWorkerFeelibInfo, + UlnManager::ERROR::invalidWorkerInfo + ); +} + +(int, slice) addWorkerFeelibToUln::fail::unregisteredBytecode(cell $storage) impure { + registerWorkerFeelibBytecode(MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD()); + + return test::handler::shouldFail( + addWorkerFeelibToUln, + MOCK_ULN_WORKER_FEELIB_INFO_WITH_STORAGE() + .cl::set(UlnWorkerFeelibInfo::workerFeelibBytecode, _getRandomCode(12345)), + UlnManager::ERROR::invalidWorkerInfo + ); +} + +(int, slice) addWorkerFeelibToUln::success::maliciousWorkerInfo(cell $storage) impure { + cell $maliciousInfo = _dupWithGarbage( + MOCK_ULN_WORKER_FEELIB_INFO_WITH_STORAGE() + .cl::set(UlnWorkerFeelibInfo::workerAddress, ARBITRARY_ADDRESS) + ); + registerWorkerFeelibBytecode(MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD()); + + return test::handler::shouldPass( + addWorkerFeelibToUln, + $maliciousInfo, + unsafeTuple([ + 0, + _newAction( + _calculateUlnAddress(getContractStorage(), DST_EID), + Uln::OP::UPDATE_WORKER_FEELIB, + MOCK_ULN_WORKER_FEELIB_INFO_WITH_STORAGE() + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) addWorkerFeelibToUln::success::wrongWorkerAddress(cell $storage) impure { + registerWorkerFeelibBytecode(MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD()); + + cell $UlnWorkerFeelibInfo = MOCK_ULN_WORKER_FEELIB_INFO_WITH_STORAGE() + .cl::set(UlnWorkerFeelibInfo::workerAddress, ARBITRARY_ADDRESS); + + return test::handler::shouldPass( + addWorkerFeelibToUln, + $UlnWorkerFeelibInfo, + unsafeTuple([ + 0, + _newAction( + _calculateUlnAddress(getContractStorage(), DST_EID), + Uln::OP::UPDATE_WORKER_FEELIB, + MOCK_ULN_WORKER_FEELIB_INFO_WITH_STORAGE() + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) setUlnTreasuryFeeBps::success::basic(cell $storage) impure { + return test::handler::shouldPass( + setUlnTreasuryFeeBps, + md::MdEid::New( + md::TreasuryFeeBps::New(TREASURY_FEE_BPS), + DST_EID + ), + unsafeTuple([ + 0, + _newAction( + _calculateUlnAddress(getContractStorage(), DST_EID), + Uln::OP::SET_TREASURY_FEE_BPS, + md::TreasuryFeeBps::New(TREASURY_FEE_BPS) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) setDefaultUlnSendConfig::success::basic(cell $storage) impure { + cell $mdEid = md::MdEid::New(MOCK_DEFAULT_ULN_SEND_CONFIG(), DST_EID); + return test::handler::shouldPass( + setDefaultUlnSendConfig, + $mdEid, + unsafeTuple([ + 0, + _newAction(_calculateUlnAddress($storage, $mdEid.cl::get(md::MdEid::eid)), + Uln::OP::SET_DEFAULT_ULN_SEND_CONFIG, + MOCK_DEFAULT_ULN_SEND_CONFIG() + )]), + getContractStorage(), + txnContext + ); +} + +(int, slice) setOAppUlnSendConfig::success::basic(cell $storage) impure { + cell $path = MOCK_SEND_PATH(); + cell $mdObj = md::MdObj::New(MOCK_CUSTOM_ULN_SEND_CONFIG(), $path); + return test::handler::shouldPass( + setOAppUlnSendConfig, + $mdObj, + unsafeTuple([ + 0, + _newAction( + _calculateUlnConnectionAddress($storage, $path), + UlnConnection::OP::SET_OAPP_ULN_SEND_CONFIG, + MOCK_CUSTOM_ULN_SEND_CONFIG() + )]), + getContractStorage(), + txnContext + ); +} + +(int, slice) claimOwnership::success::basic(cell $storage) impure { + int newOwner = ARBITRARY_ADDRESS; + cell $setAddress = md::SetAddress::New(newOwner); + + transferOwnership($setAddress); + + return test::handler::shouldPass( + claimOwnership, + empty_cell(), + unsafeTuple([ + 0, + _newAction( + UlnManager::event::OWNER_SET, + $setAddress + ) + ]), + $storage + .cl::set(UlnManager::tentativeOwner, NULLADDRESS) + .cl::set( + UlnManager::baseStorage, + $storage + .cl::get(UlnManager::baseStorage) + .cl::set(BaseStorage::owner, newOwner) + ), + txnContext + ); +} + +(int, slice) claimOwnership::revert::nullAddress(cell $storage) impure { + return test::handler::shouldFail( + claimOwnership, + empty_cell(), + UlnManager::ERROR::nullTentativeOwner + ); +} + +(int, slice) transferOwnership::success::basic(cell $storage) impure { + int newOwner = TENTATIVE_OWNER_ADDRESS; + cell $setAddress = md::SetAddress::New(newOwner); + return test::handler::shouldPass( + transferOwnership, + $setAddress, + unsafeTuple([ + 0, + _newAction( + UlnManager::event::OWNER_SET_TENTATIVE, + $setAddress + ) + ]), + $storage + .cl::set(UlnManager::tentativeOwner, newOwner), + txnContext + ); +} + +;; ============================== baseTest::getTests ============================== + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initialize::success::basic, "initialize::success::basic"]) + ;; deploy uln tests + .tpush([deployUln::success::basic, "deployUln::success::basic"]) + .tpush([deployUln::fail::invalidEid, "deployUln::fail::invalidEid"]) + ;; deploy uln connection tests + .tpush([deployUlnConnection::success::basic, "deployUlnConnection::success::basic"]) + .tpush([deployUlnConnection::fail::invalidEid, "deployUlnConnection::fail::invalidEid"]) + ;; getMsglibInfo tests + .tpush([getMsglibInfo::success::basic, "getMsglibInfo::success::basic"]) + .tpush([getMsglibInfo::fail::invalidEid, "getMsglibInfo::fail::invalidEid"]) + ;; setDefaultUlnReceiveConfig tests + .tpush([setDefaultUlnReceiveConfig::success::basic, "setDefaultUlnReceiveConfig::success::basic"]) + ;; setOAppUlnReceiveConfig tests + .tpush([setOAppUlnReceiveConfig::success::basic, "setOAppUlnReceiveConfig::success::basic"]) + ;; claimTreasuryFees tests + .tpush([claimTreasuryFees::success::basic, "claimTreasuryFees::success::basic"]) + .tpush([claimTreasuryFees::fail::insufficientBalance, "claimTreasuryFees::fail::insufficientBalance"]) + ;; setAdminWorker tests + .tpush([setAdminWorker::success::addAdminWorker, "setAdminWorker::success::addAdminWorker"]) + .tpush([setAdminWorker::revert::tooManyAdmins, "setAdminWorker::revert::tooManyAdmins"]) + .tpush([setAdminWorker::revert::malformedAdminList, "setAdminWorker::revert::malformedAdminList"]) + ;; registerUlnWorkerFeelibBytecode tests + .tpush([registerWorkerFeelibBytecode::success::basic, "registerWorkerFeelibBytecode::success::basic"]) + .tpush([registerWorkerFeelibBytecode::success::alreadyRegistered, "registerWorkerFeelibBytecode::success::alreadyRegistered"]) + ;; addWorkerFeelibToUln tests + .tpush([addWorkerFeelibToUln::success::usePermissionlessSlot, "addWorkerFeelibToUln::success::usePermissionlessSlot"]) + .tpush([addWorkerFeelibToUln::success::useAdminSlot, "addWorkerFeelibToUln::success::useAdminSlot"]) + .tpush([addWorkerFeelibToUln::fail::useAdminSlotAsNonAdmin, "addWorkerFeelibToUln::fail::useAdminSlotAsNonAdmin"]) + .tpush([addWorkerFeelibToUln::fail::unregisteredBytecode, "addWorkerFeelibToUln::fail::unregisteredBytecode"]) + .tpush([addWorkerFeelibToUln::success::maliciousWorkerInfo, "addWorkerFeelibToUln::success::maliciousWorkerInfo"]) + .tpush([addWorkerFeelibToUln::success::wrongWorkerAddress, "addWorkerFeelibToUln::success::wrongWorkerAddress"]) + ;; setTreasuryFee handler tests + .tpush([setUlnTreasuryFeeBps::success::basic, "setUlnTreasuryFeeBps::success::basic"]) + ;; setDefaultUlnSendConfig tests + .tpush([setDefaultUlnSendConfig::success::basic, "setDefaultUlnSendConfig::success::basic"]) + .tpush([setOAppUlnSendConfig::success::basic, "setOAppUlnSendConfig::success::basic"]) + ;; transferOwnership tests + .tpush([transferOwnership::success::basic, "transferOwnership::success::basic"]) + ;; claimOwnership tests + .tpush([claimOwnership::revert::nullAddress, "claimOwnership::revert::nullAddress"]) + .tpush([claimOwnership::success::basic, "claimOwnership::success::basic"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/permissions.fc new file mode 100644 index 00000000..6dbfdc8a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/permissions.fc @@ -0,0 +1,290 @@ +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/mocks.fc"; + +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../msgdata/TreasuryFeeBps.fc"; +#include "../../msgdata/UlnWorkerFeelibInfo.fc"; + +#include "../../../../../classes/msgdata/Deploy.fc"; + +#include "../../../../../../tests/baseContractTest.fc"; +#include "../../../../../../tests/protocolStorageTestUtils.fc"; +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/baseInterface.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../funC++/stdlib.fc"; +#include "../../../../../funC++/txnContext.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../classes/msgdata/AddMsglib.fc"; +#include "../../../../../classes/msgdata/MdAddress.fc"; +#include "../../../../../classes/msgdata/MdEid.fc"; +#include "../../../../../classes/msgdata/MdObj.fc"; +#include "../../../../../classes/msgdata/SetAddress.fc"; +#include "../../../../../funC++/actions/deploy.fc"; + +slice _testName() { return "ulnManagerPermissions"; } + +() _createInitializedStorage() impure { + initialize( + md::InitUlnManager::New( + MOCK_ENDPOINT_CODE(), + MOCK_CHANNEL_CODE() + ) + ); +} + +cell createContractStorage() impure { + setContractStorage( + UlnManager::New( + getCaller(), + MOCK_ULN_CODE(), + MOCK_ULN_CONNECTION_CODE(), + CONTROLLER_ADDRESS, + SRC_EID + ) + ); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) checkPermissions::deployUln::success::basic(cell $storage) impure { + return test::permissions::shouldPass( + UlnManager::OP::DEPLOY_ULN, + md::Deploy::New(INITIAL_DEPOSIT, DST_EID, NULLADDRESS) + ); +} + +(int, slice) checkPermissions::deployUln::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnManager::OP::DEPLOY_ULN, + md::Deploy::New(INITIAL_DEPOSIT, DST_EID, NULLADDRESS) + ); +} + +(int, slice) checkPermissions::setDefaultUlnReceiveConfig::success::basic(cell $storage) impure { + return test::permissions::shouldPass( + UlnManager::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG, + md::MdEid::New( + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + DST_EID + ) + ); +} + +(int, slice) checkPermissions::setDefaultUlnReceiveConfig::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnManager::OP::SET_DEFAULT_ULN_RECEIVE_CONFIG, + md::MdEid::New( + MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), + DST_EID + ) + ); +} + +(int, slice) checkPermissions::setDefaultUlnSendConfig::success::basic(cell $storage) impure { + return test::permissions::shouldPass( + UlnManager::OP::SET_DEFAULT_ULN_SEND_CONFIG, + md::MdEid::New( + MOCK_DEFAULT_ULN_SEND_CONFIG(), + DST_EID + ) + ); +} + +(int, slice) checkPermissions::setDefaultUlnSendConfig::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnManager::OP::SET_DEFAULT_ULN_SEND_CONFIG, + md::MdEid::New( + MOCK_DEFAULT_ULN_SEND_CONFIG(), + DST_EID + ) + ); +} + +(int, slice) checkPermissions::registerWorkerFeelibBytecode::success::basic(cell $storage) impure { + return test::permissions::shouldPass( + UlnManager::OP::REGISTER_WORKER_FEELIB_BYTECODE, + md::UlnWorkerFeelibBytecode::New(my_code()) + ); +} + +(int, slice) checkPermissions::registerWorkerFeelibBytecode::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnManager::OP::REGISTER_WORKER_FEELIB_BYTECODE, + md::UlnWorkerFeelibBytecode::New(my_code()) + ); +} + +(int, slice) checkPermissions::claimTreasuryFees::success::basic(cell $storage) impure { + return test::permissions::shouldPass( + UlnManager::OP::CLAIM_TREASURY_FEES, + md::CoinsAmount::New(1) + ); +} + +(int, slice) checkPermissions::claimTreasuryFees::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnManager::OP::CLAIM_TREASURY_FEES, + md::CoinsAmount::New(1) + ); +} + +(int, slice) checkPermissions::setAdminWorker::success::basic(cell $storage) impure { + return test::permissions::shouldPass( + UlnManager::OP::SET_ADMIN_WORKERS, + md::MdAddress::New( + md::Bool::New(true), + DEFAULT_EXECUTOR + ) + ); +} + +(int, slice) checkPermissions::setAdminWorker::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnManager::OP::SET_ADMIN_WORKERS, + md::MdAddress::New( + md::Bool::New(true), + DEFAULT_EXECUTOR + ) + ); +} + +(int, slice) checkPermissions::getMsglibInfo::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + MsglibManager::OP::GET_MSGLIB_INFO, + md::AddMsglib::New(ULN_MANAGER_ADDRESS, DST_EID) + ); +} + +(int, slice) checkPermissions::deployConnection::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + MsglibManager::OP::DEPLOY_CONNECTION, + md::Deploy::New(INITIAL_DEPOSIT, DST_EID, DST_OAPP) + ); +} + +(int, slice) checkPermissions::SetOAppUlnReceiveConfig::success::basic(cell $storage) impure { + spoofCaller(SRC_OAPP); + return test::permissions::shouldPass( + MsglibManager::OP::SET_OAPP_MSGLIB_RECEIVE_CONFIG, + md::MdObj::New(MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), MOCK_SEND_PATH()) + ); +} + +(int, slice) checkPermissions::SetOAppUlnReceiveConfig::revert::wrongSrcOApp(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldFail( + MsglibManager::OP::SET_OAPP_MSGLIB_RECEIVE_CONFIG, + md::MdObj::New(MOCK_DEFAULT_ULN_RECEIVE_CONFIG(), MOCK_SEND_PATH()) + ); +} + +(int, slice) checkPermissions::SetOAppUlnSendConfig::success::basic(cell $storage) impure { + spoofCaller(SRC_OAPP); + return test::permissions::shouldPass( + MsglibManager::OP::SET_OAPP_MSGLIB_SEND_CONFIG, + md::MdObj::New(MOCK_DEFAULT_ULN_SEND_CONFIG(), MOCK_SEND_PATH()) + ); +} + +(int, slice) checkPermissions::SetOAppUlnSendConfig::revert::wrongSrcOApp(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldFail( + MsglibManager::OP::SET_OAPP_MSGLIB_SEND_CONFIG, + md::MdObj::New(MOCK_DEFAULT_ULN_SEND_CONFIG(), MOCK_SEND_PATH()) + ); +} + +(int, slice) checkPermissions::setTreasuryFee::success::basic(cell $storage) impure { + return test::permissions::shouldPass( + UlnManager::OP::SET_ULN_TREASURY_FEE_BPS, + md::MdEid::New(md::TreasuryFeeBps::New(TREASURY_FEE_BPS), DST_EID) + ); +} + +(int, slice) checkPermissions::setTreasuryFee::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnManager::OP::SET_ULN_TREASURY_FEE_BPS, + md::MdEid::New(md::TreasuryFeeBps::New(TREASURY_FEE_BPS), DST_EID) + ); +} + +(int, slice) checkPermissions::claimOwnership::success(cell $storage) impure { + int tentativeOwnerAddress = NULLADDRESS; + spoofCaller(tentativeOwnerAddress); + return test::permissions::shouldPass( + UlnManager::OP::CLAIM_OWNERSHIP, + empty_cell() + ); +} + +(int, slice) checkPermissions::claimOwnership::revert::notTentativeOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnManager::OP::CLAIM_OWNERSHIP, + empty_cell() + ); +} + +(int, slice) checkPermissions::transferOwnership::success(cell $storage) impure { + return test::permissions::shouldPass( + UlnManager::OP::TRANSFER_OWNERSHIP, + md::SetAddress::New(TENTATIVE_OWNER_ADDRESS) + ); +} + +(int, slice) checkPermissions::transferOwnership::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + UlnManager::OP::TRANSFER_OWNERSHIP, + md::SetAddress::New(TENTATIVE_OWNER_ADDRESS) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([checkPermissions::deployUln::success::basic, "checkPermissions::deployUln::success::basic"]) + .tpush([checkPermissions::deployUln::revert::notOwner, "checkPermissions::deployUln::revert::notOwner"]) + .tpush([checkPermissions::setDefaultUlnReceiveConfig::success::basic, "checkPermissions::setDefaultUlnReceiveConfig::success::basic"]) + .tpush([checkPermissions::setDefaultUlnReceiveConfig::revert::notOwner, "checkPermissions::setDefaultUlnReceiveConfig::revert::notOwner"]) + .tpush([checkPermissions::setDefaultUlnSendConfig::success::basic, "checkPermissions::setDefaultUlnSendConfig::success::basic"]) + .tpush([checkPermissions::setDefaultUlnSendConfig::revert::notOwner, "checkPermissions::setDefaultUlnSendConfig::revert::notOwner"]) + .tpush([checkPermissions::registerWorkerFeelibBytecode::success::basic, "checkPermissions::registerWorkerFeelibBytecode::success::basic"]) + .tpush([checkPermissions::registerWorkerFeelibBytecode::revert::notOwner, "checkPermissions::registerWorkerFeelibBytecode::revert::notOwner"]) + .tpush([checkPermissions::claimTreasuryFees::success::basic, "checkPermissions::claimTreasuryFees::success::basic"]) + .tpush([checkPermissions::claimTreasuryFees::revert::notOwner, "checkPermissions::claimTreasuryFees::revert::notOwner"]) + .tpush([checkPermissions::setAdminWorker::success::basic, "checkPermissions::setAdminWorker::success::basic"]) + .tpush([checkPermissions::setAdminWorker::revert::notOwner, "checkPermissions::setAdminWorker::revert::notOwner"]) + .tpush([checkPermissions::getMsglibInfo::success::basic, "checkPermissions::getMsglibInfo::success::basic"]) + .tpush([checkPermissions::deployConnection::success::basic, "checkPermissions::deployConnection::success::basic"]) + .tpush([checkPermissions::SetOAppUlnReceiveConfig::success::basic, "checkPermissions::SetOAppUlnReceiveConfig::success::basic"]) + .tpush([checkPermissions::SetOAppUlnReceiveConfig::revert::wrongSrcOApp, "checkPermissions::SetOAppUlnReceiveConfig::revert::wrongSrcOApp"]) + .tpush([checkPermissions::SetOAppUlnSendConfig::success::basic, "checkPermissions::SetOAppUlnSendConfig::success::basic"]) + .tpush([checkPermissions::SetOAppUlnSendConfig::revert::wrongSrcOApp, "checkPermissions::SetOAppUlnSendConfig::revert::wrongSrcOApp"]) + .tpush([checkPermissions::setTreasuryFee::success::basic, "checkPermissions::setTreasuryFee::success::basic"]) + .tpush([checkPermissions::setTreasuryFee::revert::notOwner, "checkPermissions::setTreasuryFee::revert::notOwner"]) + .tpush([checkPermissions::claimOwnership::success, "checkPermissions::claimOwnership::success"]) + .tpush([checkPermissions::claimOwnership::revert::notTentativeOwner, "checkPermissions::claimOwnership::revert::notTentativeOwner"]) + .tpush([checkPermissions::transferOwnership::success, "checkPermissions::transferOwnership::success"]) + .tpush([checkPermissions::transferOwnership::revert::notOwner, "checkPermissions::transferOwnership::revert::notOwner"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/util.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/util.fc new file mode 100644 index 00000000..c9c198a3 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/ulnManager/tests/util.fc @@ -0,0 +1,44 @@ +#include "../../../../../../tests/consts.fc"; +#include "../../../../../../tests/mocks.fc"; + +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../msgdata/UlnWorkerFeelibInfo.fc"; + +#include "../../../../../classes/msgdata/Deploy.fc"; + +#include "../../../../../../tests/testMain.fc"; +#include "../../../../interfaces.fc"; +#include "../../../../../funC++/baseInterface.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/constants.fc"; +#include "../../../../../funC++/stdlib.fc"; +#include "../../../../../funC++/txnContext.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../funC++/actions/deploy.fc"; + +slice _testName() { return "ulnManagerUtil"; } + +cell baseTest::prepare(tuple args) impure { + int count = 0; + cell myDict = cl::dict256::New(); + while (count < 256) { + count += 1; + myDict = myDict.cl::dict256::setRef(count, my_code()); + } + return myDict; +} + +(int, slice) calculateCellTreeSize(cell $input) impure { + (int cells, _, _, int success) = compute_data_size?($input, UlnManager::CONST::MAX_CUMULATIVE_BYTECODE_CELLS); + return test::shouldBeTrue(success != 0); +} + +;; ============================== baseTest::getTests ============================== + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple().tpush([calculateCellTreeSize, "calculateCellTreeSize"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/common.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/common.fc new file mode 100644 index 00000000..85187307 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/common.fc @@ -0,0 +1,26 @@ +#include "priceFeedFeeLib/storage.fc"; + +const int TON::NativeDecimalsRate = 1000000000; ;; TONcoin is 9-decimal + +int _applyPremiumAndFloor(int feeInNanoTON, int multiplierBps, int floorMarginUSD, int nativePriceUSD) impure inline { + int feeWithMultiplier = (feeInNanoTON * multiplierBps) / 10000; + + if (max(floorMarginUSD, nativePriceUSD) <= 0) { + return feeWithMultiplier; + } + + int feeWithMargin = ((floorMarginUSD * TON::NativeDecimalsRate) / nativePriceUSD) + feeInNanoTON; + + return max(feeWithMargin, feeWithMultiplier); +} + +int _remoteToNanoton(int remoteAmount, int priceRatio) impure inline { + return (remoteAmount * priceRatio) / PRICE_RATIO_DENOMINATOR; +} + +int _applyPremiumToValue(int valueInNanoTON, int multiplierBps) impure inline { + if (valueInNanoTON > 0) { + return (valueInNanoTON * multiplierBps) / 10000; + } + return 0; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/handler.fc new file mode 100644 index 00000000..580353d7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/handler.fc @@ -0,0 +1,110 @@ +#include "../../../../../classes/msgdata/OptionsV1.fc"; +#include "../../../../../classes/msgdata/OptionsV2.fc"; + +#include "../priceFeedFeeLib/storage.fc"; +#include "../../feeLibInterface.fc"; +#include "../../msgdata/UlnWorkerFeelibInfo.fc"; + +#include "../common.fc"; +#include "storage.fc"; +#include "../../feeLibUtils.fc"; +#include "../priceFeedFeeLib/interface.fc"; + +;; From DVNFeeLib.sol +;; encoded( execute(ExecuteParam[]) ): +;; funcSigHash + params -> 4 + 32(Offset of the array) + 32(array size) + 32(first element start offset) +;; + 32(vid) + 32(target) + 32(calldata-offset) + 32(expiration) + 32(signatures-offset) = 260 +const int DvnFeelib::EXECUTE_FIXED_BYTES = 260; +const int DvnFeelib::SIGNATURE_RAW_BYTES = 65; ;; not encoded +;; verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations)\ +;; 4 + 32(header offset) + 32(payloadHash) + 32(confirmations, 8 -> 32 padded) + 32(header-size) + 96(81 -> header-padded) = 228, +;; padded to multiples of 32 = 256, encoded as bytes with an 32 byte for the bytes size = 288 +const int DvnFeelib::VERIFY_BYTES_ULN = 288; + +;; just validate options +() _decodeOption(cell $options) impure inline_ref { + int optionsType = cl::typeof($options); + if ( + (optionsType == md::OptionsV1::NAME) + | (optionsType == md::OptionsV2::NAME) + | (optionsType == cl::NULL_CLASS_NAME) + ) { + return (); + } else { + throw(UlnWorkerInterface::ERROR::UNKNOWN_OPTIONS); + } + return (); +} + +() _decodeOptions(cell $extraOptions, cell $enforcedOptions) impure inline_ref { + _decodeOption($extraOptions); + _decodeOption($enforcedOptions); + return (); +} + +int _getCallDataSize(cell $myStorage) impure inline_ref { + int quorum = $myStorage.DvnFeelib::getQuorum(); + + int totalSignatureBytes = DvnFeelib::SIGNATURE_RAW_BYTES * quorum; + ;; align on 32-byte boundary + if ((totalSignatureBytes % 32) != 0) { + totalSignatureBytes += (32 - (totalSignatureBytes % 32)); + } + return DvnFeelib::EXECUTE_FIXED_BYTES + DvnFeelib::VERIFY_BYTES_ULN + totalSignatureBytes + 32; +} + +tuple ulnWorker::quote(tuple inputs) impure method_id { + cell $myStorage = inputs.cell_at(0); + cell $priceFeedInfo = inputs.cell_at(1); + ;; cell $path = inputs.cell_at(2); + ;; int confirmations = inputs.int_at(3); + ;; int packetBytes = inputs.int_at(4); + cell $extraOptions = inputs.cell_at(5); + cell $enforcedOptions = inputs.cell_at(6); + + cell $priceFeedStorage = $priceFeedInfo.UlnWorkerFeelibInfo::getWorkerFeeLibStorage(); + + _decodeOptions($extraOptions, $enforcedOptions); + + ( + int remoteGas, + int multiplierBps, + int floorMarginUSD + ) = DvnFeelib::getRemoteGasMultiplierBpsAndFloorMarginUSD($myStorage); + + tuple returnStack = safePackedInputsRunVm( + unsafeTuple([ + $priceFeedStorage, + _getCallDataSize($myStorage), + remoteGas + ]), + PriceFeedFeeLib::method_id::estimateFee, + 1, + $priceFeedInfo.cl::get(UlnWorkerFeelibInfo::workerFeelibBytecode).begin_parse(), + 1000000 ;; gas limit does not matter here + ); + + tuple retVal = returnStack.tuple_at(0); + ;; if it's not a tuple, then the worker returned an error or went OOG + ifnot (retVal.is_tuple()) { + return unsafeTuple( + [-1, -1, null()] + ); ;; failure case so it will fail inside the _quoteWorker runVM call + } + + ;; gasFee in remote unit, (e.g., WEI for ETH) + int totalGasFeeInRemoteUnit = retVal.int_at(0); + int priceRatio = retVal.int_at(1); + int nativePriceUsd = retVal.int_at(2); + + int totalGasFee = _applyPremiumAndFloor( + _remoteToNanoton(totalGasFeeInRemoteUnit, priceRatio), + multiplierBps, + floorMarginUSD, + nativePriceUsd + ); + + return unsafeTuple( + [totalGasFee, null()] + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/main.fc new file mode 100644 index 00000000..e1e38eeb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/main.fc @@ -0,0 +1,4 @@ +#include "storage.fc"; +#include "handler.fc"; + +() main() { } \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/storage.fc new file mode 100644 index 00000000..de409f57 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/storage.fc @@ -0,0 +1,49 @@ +#include "../../../../../funC++/classlib.fc"; +#include "../../../../../funC++/stdlib.fc"; +#include "../../../../../funC++/utils.fc"; + +const int DvnFeelib::NAME = "UlnDvnFl"u; + +;; Bytecodes +const int DvnFeelib::quorum = 0; +const int DvnFeelib::remoteGas = 1; +const int DvnFeelib::multiplierBps = 2; +const int DvnFeelib::floorMarginUSD = 3; ;; uses priceFeed PRICE_RATIO_DENOMINATOR + +cell DvnFeelib::New( + int quorum, + int remoteGas, + int multiplierBps, + int floorMarginUSD +) impure inline method_id { + return cl::declare( + DvnFeelib::NAME, + unsafeTuple([ + [cl::t::uint64, quorum], ;; DvnFeelib::quorum + [cl::t::uint64, remoteGas], ;; DvnFeelib::remoteGas + [cl::t::uint16, multiplierBps], ;; DvnFeelib::multiplierBps + [cl::t::coins, floorMarginUSD] ;; DvnFeelib::floorMarginUSD + ]) + ); +} + +;; ============================== Object Getters ============================== + +const int DvnFeeLib::_quorumOffset = _HEADER_WIDTH; +const int DvnFeeLib::_remoteGasOffset = DvnFeeLib::_quorumOffset + 64; +const int DvnFeeLib::_multiplierBpsOffset = DvnFeeLib::_remoteGasOffset + 64; +const int DvnFeeLib::_floorMarginUSDOffset = DvnFeeLib::_multiplierBpsOffset + 16; + +int DvnFeelib::getQuorum(cell $self) impure inline { + return $self.cellPreloadUint64At(DvnFeeLib::_quorumOffset); +} + +;; (gas, multiplierBps, floorMarginUSD) +(int, int, int) DvnFeelib::getRemoteGasMultiplierBpsAndFloorMarginUSD(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint64At(DvnFeeLib::_remoteGasOffset), + selfSlice.preloadUint16At(DvnFeeLib::_multiplierBpsOffset), + selfSlice.preloadCoinsAt(DvnFeeLib::_floorMarginUSDOffset) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/tests/serde.fc new file mode 100644 index 00000000..e7d354cc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/tests/serde.fc @@ -0,0 +1,52 @@ +#include "../storage.fc"; +#include "../../../../../../../tests/baseSerdeTest.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/utils.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "DVN Feelib Serde"; } + +;;; ===============================TESTS========================================= + +;; DvnFeeLib has: 1 getter +;; 1 multi-getter + +(int, slice) Serde::DvnFeeLib::getQuorum(cell $unused) impure { + cell $dvnFeeLib = DvnFeelib::New(1, 2, 3, 4); + + return test::getData::equal( + $dvnFeeLib, + DvnFeelib::getQuorum, + DvnFeelib::quorum + ); +} + +(int, slice) Serde::DvnFeeLib::getRemoteGasMultiplierBpsAndFloorMarginUSD(cell $unused) impure { + cell $dvnFeeLib = DvnFeelib::New(1, 2, 3, 4); + + ( + int gas, + int multiplierBps, + int floorMarginUSD + ) = DvnFeelib::getRemoteGasMultiplierBpsAndFloorMarginUSD($dvnFeeLib); + + return test::multiget::equal( + $dvnFeeLib, + unsafeTuple([ + DvnFeelib::remoteGas, + DvnFeelib::multiplierBps, + DvnFeelib::floorMarginUSD + ]), + unsafeTuple([gas, multiplierBps, floorMarginUSD]) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::DvnFeeLib::getQuorum, "Serde::DvnFeeLib::getQuorum"]) + .tpush([Serde::DvnFeeLib::getRemoteGasMultiplierBpsAndFloorMarginUSD, "Serde::DvnFeeLib::getRemoteGasMultiplierBpsAndFloorMarginUSD"]) + ; +} + diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/handler.fc new file mode 100644 index 00000000..4ff0f742 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/handler.fc @@ -0,0 +1,175 @@ +#include "../../../../../classes/lz/Path.fc"; +#include "../../../../../classes/msgdata/OptionsV1.fc"; +#include "../../../../../classes/msgdata/OptionsV2.fc"; + +#include "../../feeLibInterface.fc"; +#include "../priceFeedFeeLib/interface.fc"; +#include "../priceFeedFeeLib/storage.fc"; +#include "../../msgdata/UlnWorkerFeelibInfo.fc"; + +#include "../common.fc"; +#include "storage.fc"; +#include "../../feeLibUtils.fc"; +#include "../../../../../funC++/utils.fc"; + +int isV1Eid(int eid) impure inline { + return (eid < 30000); +} + +(int, int) _decodeOption(int lzComposeBaseGas, cell $options, cell $path) impure inline { + int totalGas = 0; + int totalDstAmount = 0; + + int optionsType = cl::typeof($options); + + if (optionsType == md::OptionsV1::NAME) { + ( + int lzReceiveGas, + int lzReceiveValue, + int nativeDropAmount + ) = $options.md::OptionsV1::decodeCoins(); + + ;; LayerZero V1 does not support value on lzReceive + throw_if( + UlnWorkerInterface::ERROR::INVALID_OPTIONS, + (lzReceiveValue > 0) & isV1Eid($path.lz::Path::getDstEid()) + ); + + totalGas += lzReceiveGas; + + totalDstAmount += lzReceiveValue + nativeDropAmount; + + } elseif (optionsType == md::OptionsV2::NAME) { + ( + int lzComposeGas, + int lzComposeValue, + int lzReceiveGas, + int lzReceiveValue, + int nativeDropAmount + ) = $options.md::OptionsV2::decodeCoins(); + + throw_if( + UlnWorkerInterface::ERROR::ZERO_LZ_COMPOSE_GAS_PROVIDED, + (lzComposeGas == 0) & (lzComposeValue > 0) + ); + + totalGas += lzReceiveGas; + + totalDstAmount += lzReceiveValue + nativeDropAmount; + + if (lzComposeGas > 0) { + totalGas += lzComposeGas + lzComposeBaseGas; + totalDstAmount += lzComposeValue; + } + + } elseif (optionsType == cl::NULL_CLASS_NAME) { + return (0, 0); + } else { + throw(UlnWorkerInterface::ERROR::UNKNOWN_OPTIONS); + } + + return (totalGas, totalDstAmount); +} + +(int, int) _decodeOptions( + int lzReceiveBaseGas, + int lzComposeBaseGas, + cell $extraOptions, + cell $enforcedOptions, + cell $path +) impure inline { + int totalGas = 0; + int totalDstAmount = 0; + + ifnot ($extraOptions.cl::isNullObject()) { + (int _totalGas, int _totalDstAmount) = _decodeOption( + lzComposeBaseGas, + $extraOptions, + $path + ); + + totalGas += _totalGas; + totalDstAmount += _totalDstAmount; + } + + ifnot ($enforcedOptions.cl::isNullObject()) { + (int _totalGas, int _totalDstAmount) = _decodeOption( + lzComposeBaseGas, + $enforcedOptions, + $path + ); + totalGas += _totalGas; + totalDstAmount += _totalDstAmount; + } + + return (totalGas + lzReceiveBaseGas, totalDstAmount); +} + +;; current gas consumed: 16276 +tuple ulnWorker::quote(tuple inputs) impure method_id { + cell $myStorage = inputs.cell_at(0); + cell $priceFeedInfo = inputs.cell_at(1); + cell $path = inputs.cell_at(2); + ;; int confirmations = inputs.int_at(3); + int packetBytes = inputs.int_at(4); + cell $extraOptions = inputs.cell_at(5); + cell $enforcedOptions = inputs.cell_at(6); + + cell $priceFeedStorage = $priceFeedInfo.UlnWorkerFeelibInfo::getWorkerFeeLibStorage(); + + ( + int lzReceiveBaseGas, + int multiplierBps, + int floorMarginUSD, + int nativeCap, + int lzComposeBaseGas + ) = ExecutorFeelib::deserializeStorage($myStorage); + + ( + int totalRemoteGas, + int totalRemoteValue + ) = _decodeOptions(lzReceiveBaseGas, lzComposeBaseGas, $extraOptions, $enforcedOptions, $path); + + throw_if(UlnWorkerInterface::ERROR::NATIVE_CAP_EXCEEDED, totalRemoteValue > nativeCap); + + tuple returnStack = safePackedInputsRunVm( + unsafeTuple([ + $priceFeedStorage, + packetBytes, + totalRemoteGas + ]), + PriceFeedFeeLib::method_id::estimateFee, + 1, + $priceFeedInfo.cl::get(UlnWorkerFeelibInfo::workerFeelibBytecode).begin_parse(), + 1000000 ;; gas limit does not matter here + ); + + tuple retVal = returnStack.tuple_at(0); + ;; if it's not a tuple, then the worker returned an error or went OOG + ifnot (retVal.is_tuple()) { + return unsafeTuple( + [-1, -1, null()] + ); ;; failure case so it will fail inside the _quoteWorker runVM call + } + + int totalGasFeeInRemoteUnit = retVal.int_at(0); + int priceRatio = retVal.int_at(1); + int nativePriceUsd = retVal.int_at(2); + + int totalGasFeeInNanoton = _applyPremiumAndFloor( + _remoteToNanoton(totalGasFeeInRemoteUnit, priceRatio), + multiplierBps, + floorMarginUSD, + nativePriceUsd + ); + + ;; match order of operations of EVM at some loss of precision + int totalValueFeeInNanoton = _applyPremiumToValue( + _remoteToNanoton(totalRemoteValue, priceRatio), + multiplierBps + ); + + return unsafeTuple( + [totalGasFeeInNanoton + totalValueFeeInNanoton, null()] + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/main.fc new file mode 100644 index 00000000..e1e38eeb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/main.fc @@ -0,0 +1,4 @@ +#include "storage.fc"; +#include "handler.fc"; + +() main() { } \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/storage.fc new file mode 100644 index 00000000..45c6c08a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/storage.fc @@ -0,0 +1,48 @@ +#include "../../../../../funC++/classlib.fc"; + +const int ExecutorFeelib::NAME = "UlnExecutr"u; + +;; Bytecodes +const int ExecutorFeelib::lzReceiveBaseGas = 0; +const int ExecutorFeelib::multiplierBps = 1; +const int ExecutorFeelib::floorMarginUSD = 2; ;; uses priceFeed PRICE_RATIO_DENOMINATOR +const int ExecutorFeelib::nativeCap = 3; +const int ExecutorFeelib::lzComposeBaseGas = 4; + +cell ExecutorFeelib::New( + int lzReceiveBaseGas, + int multiplierBps, + int floorMarginUSD, + int nativeCap, + int lzComposeBaseGas +) impure inline method_id { + return cl::declare( + ExecutorFeelib::NAME, + unsafeTuple([ + [cl::t::uint64, lzReceiveBaseGas], ;; ExecutorFeelib::lzReceiveBaseGas + [cl::t::uint16, multiplierBps], ;; ExecutorFeelib::multiplierBps + [cl::t::coins, floorMarginUSD], ;; ExecutorFeelib::floorMarginUSD + [cl::t::coins, nativeCap], ;; ExecutorFeelib::nativeCap + [cl::t::uint64, lzComposeBaseGas] ;; ExecutorFeelib::lzComposeBaseGas + ]) + ); +} + +;; ============================== Object Getters ========================================= + +const int ExecutorFeelib::_lzReceiveBaseGasOffset = _HEADER_WIDTH; +const int ExecutorFeelib::_multiplierBpsOffset = ExecutorFeelib::_lzReceiveBaseGasOffset + 64; +const int ExecutorFeelib::_floorMarginUSDOffset = ExecutorFeelib::_multiplierBpsOffset + 16; +const int ExecutorFeelib::_nativeCapOffset = ExecutorFeelib::_floorMarginUSDOffset + 128; +const int ExecutorFeelib::_lzComposeBaseGasOffset = ExecutorFeelib::_nativeCapOffset + 128; + +(int, int, int, int, int) ExecutorFeelib::deserializeStorage(cell $storage) impure inline { + slice selfSlice = $storage.begin_parse(); + return ( + selfSlice.preloadUint64At(ExecutorFeelib::_lzReceiveBaseGasOffset), + selfSlice.preloadUint16At(ExecutorFeelib::_multiplierBpsOffset), + selfSlice.preloadCoinsAt(ExecutorFeelib::_floorMarginUSDOffset), + selfSlice.preloadCoinsAt(ExecutorFeelib::_nativeCapOffset), + selfSlice.preloadUint64At(ExecutorFeelib::_lzComposeBaseGasOffset) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/tests/serde.fc new file mode 100644 index 00000000..4e7a100b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/tests/serde.fc @@ -0,0 +1,46 @@ +#include "../../../../../../../tests/baseSerdeTest.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../funC++/classlib.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Pricefeed Feelib Serde"; } + +;;; ===============================TESTS========================================= + +;; ExecutorFeeLib has 1 getter +(int, slice) Serde::ExecutorFeelib:::deserializeStorage(cell $unused) impure { + cell $executorFeeLib = ExecutorFeelib::New(100000, 1, 2, 3, 4); + + ( + int lzReceiveBaseGas, + int multiplierBps, + int floorMarginUSD, + int nativeCap, + int lzComposeBaseGas + ) = ExecutorFeelib::deserializeStorage($executorFeeLib); + + return test::multiget::equal( + $executorFeeLib, + unsafeTuple([ + ExecutorFeelib::lzReceiveBaseGas, + ExecutorFeelib::multiplierBps, + ExecutorFeelib::floorMarginUSD, + ExecutorFeelib::nativeCap, + ExecutorFeelib::lzComposeBaseGas + ]), + unsafeTuple([ + lzReceiveBaseGas, + multiplierBps, + floorMarginUSD, + nativeCap, + lzComposeBaseGas + ]) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::ExecutorFeelib:::deserializeStorage, "Serde::ExecutorFeelib:::deserializeStorage"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/main.fc new file mode 100644 index 00000000..cfde5a20 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/main.fc @@ -0,0 +1,3 @@ +#include "ulnSendWorkerV1.fc"; + +() main() { } \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/maliciousFeeLib/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/maliciousFeeLib/handler.fc new file mode 100644 index 00000000..d5a63296 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/maliciousFeeLib/handler.fc @@ -0,0 +1,9 @@ +#include "../../../../../funC++/utils.fc"; + +tuple ulnWorker::quote(tuple inputs) impure method_id { + int x = 0; + while (true) { + x += 1; + } + return unsafeTuple([x]); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/maliciousFeeLib/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/maliciousFeeLib/main.fc new file mode 100644 index 00000000..4f5e3588 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/maliciousFeeLib/main.fc @@ -0,0 +1,3 @@ +#include "handler.fc"; + +() main() { } \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/handler.fc new file mode 100644 index 00000000..b2221079 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/handler.fc @@ -0,0 +1,38 @@ +#include "../storage.fc"; +#include "../extensions/ArbitrumPriceFeedExtension.fc"; + +const int ARBITRUM_COMPRESSION_PERCENT = 47; + +;; Arbitrum model +tuple priceFeedFeeLib::estimateFee(tuple inputs) impure method_id(11111) { + cell $priceFeedStorage = inputs.cell_at(0); + int callDataSize = inputs.int_at(1); + int remoteGas = inputs.int_at(2); + + ( + int priceRatio, + int gasPriceInRemoteUnit, + int gasPerByte, + int nativePriceUsd, + cell $arbitrumExtension, + _ + ) = PriceFeedFeelib::deserialize($priceFeedStorage); + + ( + int gasPerL2Tx, + int gasPerL1CallDataByte + ) = ArbitrumPriceFeedExtension::deserialize($arbitrumExtension); + + int gasForL1CallData = ((callDataSize * ARBITRUM_COMPRESSION_PERCENT) / 100) * gasPerL1CallDataByte; + int gasForL2CallData = callDataSize * gasPerByte; + + int gasFeeInRemoteUnit = (remoteGas + gasPerL2Tx + gasForL1CallData + gasForL2CallData) * gasPriceInRemoteUnit; + + return unsafeTuple( + [ + gasFeeInRemoteUnit, ;; gasFee in Remote Unit, (e.g., WEI for ETH) + priceRatio, ;; priceRatio + nativePriceUsd ;; Native price in USD + ] + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/main.fc new file mode 100644 index 00000000..898bc4a7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/main.fc @@ -0,0 +1,2 @@ +#include "handler.fc"; +() main() { } \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/tests/main.fc new file mode 100644 index 00000000..1577e385 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/tests/main.fc @@ -0,0 +1,16 @@ +#include "../../../../../../../../tests/testMain.fc"; +#include "../../../../../../../../tests/mocks.fc"; +#include "../../../../../../../funC++/classlib.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Pricefeed Arbitrum Feelib"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} +;;; ===============================TESTS========================================= + +tuple baseTest::getTests() impure { + return empty_tuple(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/handler.fc new file mode 100644 index 00000000..4b7da098 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/handler.fc @@ -0,0 +1,27 @@ +#include "../storage.fc"; + +;; Default model + +tuple priceFeedFeeLib::estimateFee(tuple inputs) impure method_id(11111) { + cell $priceFeedStorage = inputs.cell_at(0); + int callDataSize = inputs.int_at(1); + int remoteGas = inputs.int_at(2); + + ( + int priceRatio, + int gasPriceInRemoteUnit, + int gasPerByte, + int nativePriceUsd + ) = PriceFeedFeelib::deserializeInts($priceFeedStorage); + + int gasForCallData = callDataSize * gasPerByte; + int gasFeeInRemoteUnit = (gasForCallData + remoteGas) * gasPriceInRemoteUnit; + + return unsafeTuple( + [ + gasFeeInRemoteUnit, ;; gasFee in Remote Unit, (e.g., WEI for ETH) + priceRatio, ;; priceRatio + nativePriceUsd ;; Native price in USD + ] + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/main.fc new file mode 100644 index 00000000..898bc4a7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/main.fc @@ -0,0 +1,2 @@ +#include "handler.fc"; +() main() { } \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/tests/main.fc new file mode 100644 index 00000000..30938e9f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/tests/main.fc @@ -0,0 +1,16 @@ +#include "../../../../../../../../tests/testMain.fc"; +#include "../../../../../../../../tests/mocks.fc"; +#include "../../../../../../../funC++/classlib.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Pricefeed Default Feelib"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} +;;; ===============================TESTS========================================= + +tuple baseTest::getTests() impure { + return empty_tuple(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/extensions/ArbitrumPriceFeedExtension.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/extensions/ArbitrumPriceFeedExtension.fc new file mode 100644 index 00000000..e3dd1b43 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/extensions/ArbitrumPriceFeedExtension.fc @@ -0,0 +1,27 @@ +#include "../../../../../../funC++/classlib.fc"; + +const int ArbitrumPriceFeedExtension::NAME = "ArbFeeExt"u; + +;; uint64 gasPerL2Tx; // L2 overhead +;; uint32 gasPerL1CallDataByte; + +const int ArbitrumPriceFeedExtension::gasPerL2Tx = 0; +const int ArbitrumPriceFeedExtension::gasPerL1CallDataByte = 1; + +cell ArbitrumPriceFeedExtension::New( + int gasPerL2Tx, + int gasPerL1CallDataByte +) impure method_id { + return cl::declare( + ArbitrumPriceFeedExtension::NAME, + unsafeTuple([ + [cl::t::uint64, gasPerL2Tx], ;; ArbitrumPriceFeedExtension::gasPerL2Tx + [cl::t::uint32, gasPerL1CallDataByte] ;; ArbitrumPriceFeedExtension::gasPerL1CallDataByte + ]) + ); +} + +(int, int) ArbitrumPriceFeedExtension::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse().skip_bits(_HEADER_WIDTH); + return (selfSlice~load_uint64(), selfSlice~load_uint32()); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/interface.fc new file mode 100644 index 00000000..eb24729c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/interface.fc @@ -0,0 +1 @@ +const int PriceFeedFeeLib::method_id::estimateFee = 11111; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/handler.fc new file mode 100644 index 00000000..33699054 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/handler.fc @@ -0,0 +1,45 @@ +#include "../storage.fc"; + +const int OP_L1_OVERHEAD = 3188; ;; 2100 + 68 * 16 + +;; Optimism model +tuple priceFeedFeeLib::estimateFee(tuple inputs) impure method_id(11111) { + cell $priceFeedStorage = inputs.cell_at(0); + int callDataSize = inputs.int_at(1); + int remoteGas = inputs.int_at(2); + + ;; ETH (L1) values + ( + int ethPriceRatio, + int ethGasPriceInRemoteUnit, + int ethGasPerByte, + _, _, + cell $optimismExtension + ) = PriceFeedFeelib::deserialize($priceFeedStorage); + + ;; OP (L2) values + ( + int opPriceRatio, + int opGasPriceInRemoteUnit, + int opGasPerByte, + int opNativePriceUsd, _, _ + ) = PriceFeedFeelib::deserialize($optimismExtension); + + int gasForL1CallData = (callDataSize * ethGasPerByte) + OP_L1_OVERHEAD; + int l1FeeInEthUnit = gasForL1CallData * ethGasPriceInRemoteUnit; + ;; (fee/eth) * (eth/ton) / (op/ton) = fee/op + int l1FeeInOpUnit = ((l1FeeInEthUnit * ethPriceRatio) / opPriceRatio); ;; convert from ETH units to TON units and back to OP units + + int gasForL2CallData = callDataSize * opGasPerByte; + int l2FeeInOpUnit = (gasForL2CallData + remoteGas) * opGasPriceInRemoteUnit; + + int gasFeeInOpUnit = l1FeeInOpUnit + l2FeeInOpUnit; + + return unsafeTuple( + [ + gasFeeInOpUnit, ;; gasFee in Remote Unit, (e.g., WEI for ETH) + opPriceRatio, ;; priceRatio + opNativePriceUsd ;; Op Native price in USD + ] + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/main.fc new file mode 100644 index 00000000..898bc4a7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/main.fc @@ -0,0 +1,2 @@ +#include "handler.fc"; +() main() { } \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/tests/main.fc new file mode 100644 index 00000000..609d40aa --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/tests/main.fc @@ -0,0 +1,16 @@ +#include "../../../../../../../../tests/testMain.fc"; +#include "../../../../../../../../tests/mocks.fc"; +#include "../../../../../../../funC++/classlib.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Pricefeed Optimism Feelib"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} +;;; ===============================TESTS========================================= + +tuple baseTest::getTests() impure { + return empty_tuple(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/storage.fc new file mode 100644 index 00000000..31efac7a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/storage.fc @@ -0,0 +1,161 @@ +#include "../../../../../funC++/classlib.fc"; + +const int PriceFeedFeelib::NAME = "PFFeelib"u; + +const int PRICE_RATIO_DENOMINATOR = 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 100; + +;; Bytecodes +const int PriceFeedFeelib::priceRatio = 0; +const int PriceFeedFeelib::gasPriceInRemoteUnit = 1; +const int PriceFeedFeelib::gasPerByte = 2; +const int PriceFeedFeelib::nativePriceUsd = 3; +const int PriceFeedFeelib::arbitrumExtension = 4; +const int PriceFeedFeelib::optimismExtension = 5; + +;; @owner LayerZero admin EOA +cell PriceFeedFeelib::New( + int priceRatio, + int gasPriceInRemoteUnit, + int gasPerByte, + int nativePriceUsd, + cell arbitrumExtension, + cell optimismExtension +) impure method_id { + return cl::declare( + PriceFeedFeelib::NAME, + unsafeTuple([ + [cl::t::coins, priceRatio], ;; PriceFeedFeelib::priceRatio + [cl::t::uint64, gasPriceInRemoteUnit], ;; PriceFeedFeelib::gasPriceInRemoteUnit + [cl::t::uint32, gasPerByte], ;; PriceFeedFeelib::gasPerByte + [cl::t::coins, nativePriceUsd], ;; PriceFeedFeelib::nativePriceUsd + [cl::t::objRef, arbitrumExtension], ;; PriceFeedFeelib::arbitrumExtension + [cl::t::objRef, optimismExtension] ;; PriceFeedFeelib::optimismExtension + ]) + ); +} + +const int PriceFeedFeelib::_priceRatioOffset = _HEADER_WIDTH; +const int PriceFeedFeelib::_gasPriceInRemoteUnitOffset = PriceFeedFeelib::_priceRatioOffset + 128; +const int PriceFeedFeelib::_gasPerByteOffset = PriceFeedFeelib::_gasPriceInRemoteUnitOffset + 64; +const int PriceFeedFeelib::_nativePriceUsdOffset = PriceFeedFeelib::_gasPerByteOffset + 32; + + +;; ============================== Object Getters ========================================= + +;; nativePriceUsd +int PriceFeedFeelib::getNativePriceUsd(cell $self) impure inline { + return $self.cellPreloadCoinsAt(PriceFeedFeelib::_nativePriceUsdOffset); +} + +;; ============================== Object Multi-Getters ========================================= + +;; (priceRatio, gasPriceInRemoteUnit, gasPerByte) +(int, int, int) PriceFeedFeelib::utils::getFirstThreeFields(cell $self) impure inline { + ;; slice selfSlice = $self.begin_parse().skip_bits(_HEADER_WIDTH); + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadCoinsAt(PriceFeedFeelib::_priceRatioOffset), + selfSlice.preloadUint64At(PriceFeedFeelib::_gasPriceInRemoteUnitOffset), + selfSlice.preloadUint32At(PriceFeedFeelib::_gasPerByteOffset) + ); +} + +cell PriceFeedFeelib::getArbitrumExtension(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +cell PriceFeedFeelib::getOptimismExtension(cell $self) impure inline { + return $self.cellPreloadRefAt(1); +} + +;; (priceRatio, gasPriceInRemoteUnit, gasPerByte, nativePriceUsd) +(int, int, int, int) PriceFeedFeelib::deserializeInts(cell $storage) impure inline { + slice selfSlice = $storage.begin_parse(); + return ( + selfSlice.preloadCoinsAt(PriceFeedFeelib::_priceRatioOffset), + selfSlice.preloadUint64At(PriceFeedFeelib::_gasPriceInRemoteUnitOffset), + selfSlice.preloadUint32At(PriceFeedFeelib::_gasPerByteOffset), + selfSlice.preloadCoinsAt(PriceFeedFeelib::_nativePriceUsdOffset) + ); +} + +;; (priceRatio, gasPriceInRemoteUnit, gasPerByte, nativePriceUsd, arbitrumExtension, optimismExtension) +(int, int, int, int, cell, cell) PriceFeedFeelib::deserialize(cell $storage) impure inline { + slice selfSlice = $storage.begin_parse(); + return ( + selfSlice.preloadCoinsAt(PriceFeedFeelib::_priceRatioOffset), + selfSlice.preloadUint64At(PriceFeedFeelib::_gasPriceInRemoteUnitOffset), + selfSlice.preloadUint32At(PriceFeedFeelib::_gasPerByteOffset), + selfSlice.preloadCoinsAt(PriceFeedFeelib::_nativePriceUsdOffset), + selfSlice.preloadRefAt(0), + selfSlice.preloadRefAt(1) + ); +} + +;; ============================== Object Setters ========================================= + +;; (priceRatio, gasPriceInRemoteUnit, gasPerByte) +cell PriceFeedFeelib::setFirstThreeFields( + cell $self, + int priceRatio, + int gasPriceInRemoteUnit, + int gasPerByte +) impure inline { + slice selfSlice = $self.begin_parse(); + return begin_cell() + .store_slice(selfSlice.scutfirst(_HEADER_WIDTH, 2)) ;; header + refs + .store_uint128(priceRatio) ;; new priceRatio + .store_uint64(gasPriceInRemoteUnit) ;; new gasPriceInRemoteUnit + .store_uint32(gasPerByte) ;; new gasPerByte + .store_slice(selfSlice.scutlast(128, 0)) ;; last 128 bits = old nativePriceUsd + .end_cell(); +} + +cell PriceFeedFeelib::setNativePriceUsd(cell $self, int nativePriceUsd) impure inline { + slice selfSlice = $self.begin_parse(); + return begin_cell() + .store_slice(selfSlice.scutfirst(PriceFeedFeelib::_nativePriceUsdOffset, 2)) ;; everything up to nativePriceUsd + .store_uint128(nativePriceUsd) ;; new nativePriceUsd + .end_cell(); +} + +cell PriceFeedFeelib::setPricesAndOptimismExtension( + cell $self, + int priceRatio, + int gasPriceInRemoteUnit, + int gasPerByte, + cell $optimismExtension +) impure inline { + slice selfSlice = $self.begin_parse(); + return begin_cell() + .store_slice(selfSlice.scutfirst(_HEADER_WIDTH, 1)) ;; header + old arbitrumExtension + .store_uint128(priceRatio) ;; new priceRatio + .store_uint64(gasPriceInRemoteUnit) ;; new gasPriceInRemoteUnit + .store_uint32(gasPerByte) ;; new gasPerByte + .store_slice(selfSlice.scutlast(128, 0)) ;; last 128 bits = old nativePriceUsd + .store_ref($optimismExtension) ;; new optimismExtension + .end_cell(); +} + +cell PriceFeedFeelib::setNativePriceUsdAndOptimismExtension( + cell $self, + int nativePriceUsd, + cell $optimismExtension +) impure inline { + slice selfSlice = $self.begin_parse(); + return begin_cell() + .store_slice(selfSlice.scutfirst(PriceFeedFeelib::_nativePriceUsdOffset, 1)) ;; bits up to nativePriceUsd + old arbitrumExtension + .store_uint128(nativePriceUsd) ;; new nativePriceUsd + .store_ref($optimismExtension) ;; new optimismExtension + .end_cell(); +} + + +cell PriceFeedFeelib::setArbitrumExtension(cell $self, cell $arbitrumExtension) impure inline { + slice selfSlice = $self.begin_parse(); + return begin_cell() + .store_slice(selfSlice.sskipfirst(0, 2)) ;; same bits, no refs + .store_ref($arbitrumExtension) ;; new arbitrumExtension + .store_ref(selfSlice.preloadRefAt(1)) ;; same optimismExtension + .end_cell(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/tests/serde.fc new file mode 100644 index 00000000..f411cb2e --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/tests/serde.fc @@ -0,0 +1,227 @@ +#include "../../../../../../../tests/baseSerdeTest.fc"; +#include "../../../../../../../tests/mocks.fc"; +#include "../../../../../../funC++/classlib.fc"; +#include "../../../../../../funC++/stdlib.fc"; +#include "../../../../../../funC++/utils.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Executor Feelib Serde"; } + +;;; ===============================TESTS========================================= + +;; PriceFeedFeelib has 1 getter +;; 2 multi-getter +;; 2 setters + +(int, slice) Serde::PriceFeedFeeLib::getNativePriceUsd(cell $unused) impure { + cell $priceFeedFeelib = MOCK_DEFAULT_PRICE_FEED_STORAGE(); + return test::getData::equal( + $priceFeedFeelib, + PriceFeedFeelib::getNativePriceUsd, + PriceFeedFeelib::nativePriceUsd + ); +} + +(int, slice) Serde::PriceFeedFeeLib::getFirstThreeFields(cell $unused) impure { + cell $priceFeedFeelib = MOCK_DEFAULT_PRICE_FEED_STORAGE(); + + ( + int priceRatio, + int gasPriceInUnit, + int gasPerByte + ) = PriceFeedFeelib::utils::getFirstThreeFields($priceFeedFeelib); + + return test::multiget::equal( + $priceFeedFeelib, + unsafeTuple([PriceFeedFeelib::priceRatio, + PriceFeedFeelib::gasPriceInRemoteUnit, + PriceFeedFeelib::gasPerByte]), + unsafeTuple([ + priceRatio, + gasPriceInUnit, + gasPerByte + ]) + ); +} + +(int, slice) Serde::PriceFeedFeelib::getArbitrumExtension(cell $unused) impure { + cell $priceFeedFeeLib = MOCK_DEFAULT_PRICE_FEED_STORAGE_ARB(); + return test::getRef::equal( + $priceFeedFeeLib, + PriceFeedFeelib::getArbitrumExtension, + PriceFeedFeelib::arbitrumExtension + ); +} + +(int, slice) Serde::PriceFeedFeelib::getOptimismExtension(cell $unused) impure { + cell $priceFeedFeeLib = MOCK_DEFAULT_PRICE_FEED_STORAGE_OP(); + return test::getRef::equal( + $priceFeedFeeLib, + PriceFeedFeelib::getOptimismExtension, + PriceFeedFeelib::optimismExtension + ); +} + +(int, slice) Serde::PriceFeedFeelib::deserializeInts(cell $unused) impure { + cell $priceFeedFeeLib = MOCK_DEFAULT_PRICE_FEED_STORAGE(); + + ( + int priceRatio, + int gasPriceInUnit, + int gasPerByte, + int nativePriceUsd + ) = PriceFeedFeelib::deserializeInts($priceFeedFeeLib); + + return test::multiget::equal( + $priceFeedFeeLib, + unsafeTuple([ + PriceFeedFeelib::priceRatio, + PriceFeedFeelib::gasPriceInRemoteUnit, + PriceFeedFeelib::gasPerByte, + PriceFeedFeelib::nativePriceUsd + ]), + unsafeTuple([priceRatio, gasPriceInUnit, gasPerByte, nativePriceUsd]) + ); +} + +(int, slice) Serde::PriceFeedFeeLib::deserialize(cell $unused) impure { + cell $priceFeedFeeLib = MOCK_DEFAULT_PRICE_FEED_STORAGE(); + ( + int priceRatio, + int gasPriceInUnit, + int gasPerByte, + int nativePriceUsd, + cell arbitrumExtension, + cell optimismExtension + ) = PriceFeedFeelib::deserialize($priceFeedFeeLib); + + return test::multiget::equal( + $priceFeedFeeLib, + unsafeTuple([ + PriceFeedFeelib::priceRatio, + PriceFeedFeelib::gasPriceInRemoteUnit, + PriceFeedFeelib::gasPerByte, + PriceFeedFeelib::nativePriceUsd, + PriceFeedFeelib::arbitrumExtension, + PriceFeedFeelib::optimismExtension + ]), + unsafeTuple([priceRatio, gasPriceInUnit, gasPerByte, nativePriceUsd, arbitrumExtension, optimismExtension]) + ); +} + +(int, slice) Serde::PriceFeedFeeLib::setFirstThreeFields(cell $unused) impure { + int priceRatio = 89; + int gasPriceInUnit = 103; + int gasPerByte = 201; + cell $initPriceFeedFeelib = MOCK_DEFAULT_PRICE_FEED_STORAGE(); + cell $expectedPriceFeedFeelib = $initPriceFeedFeelib + .cl::set(PriceFeedFeelib::priceRatio, priceRatio) + .cl::set(PriceFeedFeelib::gasPriceInRemoteUnit, gasPriceInUnit) + .cl::set(PriceFeedFeelib::gasPerByte, gasPerByte); + cell $newPriceFeedFeelib = PriceFeedFeelib::setFirstThreeFields( + $initPriceFeedFeelib, + priceRatio, + gasPriceInUnit, + gasPerByte + ); + + return test::set::equal( + $expectedPriceFeedFeelib, + $newPriceFeedFeelib + ); +} + +(int, slice) Serde::PriceFeedFeeLib::setNativePriceUsd(cell $unused) impure { + cell $initPriceFeedFeelib = MOCK_DEFAULT_PRICE_FEED_STORAGE(); + + cell $expectedPriceFeedFeelib = $initPriceFeedFeelib.cl::set(PriceFeedFeelib::nativePriceUsd, 47); + cell $newPriceFeedFeelib = PriceFeedFeelib::setNativePriceUsd($initPriceFeedFeelib, 47); + + return test::set::equal( + $expectedPriceFeedFeelib, + $newPriceFeedFeelib + ); +} + +(int, slice) Serde::PriceFeedFeeLib::setPricesAndOptimismExtension(cell $unused) impure { + int priceRatio = 89; + int gasPriceInUnit = 103; + int gasPerByte = 201; + cell $initPriceFeedFeelib = MOCK_DEFAULT_PRICE_FEED_STORAGE_OP(); + + cell $newOptimismExtension = MOCK_CUSTOM_PRICE_FEED_STORAGE_OP() + .cl::get(PriceFeedFeelib::optimismExtension); + + cell $expectedPriceFeedFeelib = $initPriceFeedFeelib + .cl::set(PriceFeedFeelib::priceRatio, priceRatio) + .cl::set(PriceFeedFeelib::gasPriceInRemoteUnit, gasPriceInUnit) + .cl::set(PriceFeedFeelib::gasPerByte, gasPerByte) + .cl::set(PriceFeedFeelib::optimismExtension, $newOptimismExtension); + + cell $newPriceFeedFeelib = PriceFeedFeelib::setPricesAndOptimismExtension( + $initPriceFeedFeelib, + priceRatio, + gasPriceInUnit, + gasPerByte, + $newOptimismExtension + ); + + return test::set::equal( + $expectedPriceFeedFeelib, + $newPriceFeedFeelib + ); +} + +(int, slice) Serde::PriceFeedFeeLib::setNativePriceUsdAndOptimismExtension(cell $unused) impure { + int nativePriceUsd = 47; + cell $initPriceFeedFeelib = MOCK_DEFAULT_PRICE_FEED_STORAGE_OP(); + + cell $newOptimismExtension = MOCK_CUSTOM_PRICE_FEED_STORAGE_OP() + .cl::get(PriceFeedFeelib::optimismExtension); + + cell $expectedPriceFeedFeelib = $initPriceFeedFeelib + .cl::set(PriceFeedFeelib::nativePriceUsd, nativePriceUsd) + .cl::set(PriceFeedFeelib::optimismExtension, $newOptimismExtension); + + cell $newPriceFeedFeelib = PriceFeedFeelib::setNativePriceUsdAndOptimismExtension( + $initPriceFeedFeelib, + nativePriceUsd, + $newOptimismExtension + ); + + return test::set::equal( + $expectedPriceFeedFeelib, + $newPriceFeedFeelib + ); +} + +(int, slice) Serde::PriceFeedFeeLib::setArbitrumExtension(cell $unused) impure { + cell $initPriceFeedFeelib = MOCK_DEFAULT_PRICE_FEED_STORAGE_ARB(); + cell $newArbitrumExtension = MOCK_CUSTOM_PRICE_FEED_STORAGE_ARB() + .cl::get(PriceFeedFeelib::arbitrumExtension); + + cell $expectedPriceFeedFeelib = $initPriceFeedFeelib + .cl::set(PriceFeedFeelib::arbitrumExtension, $newArbitrumExtension); + cell $newPriceFeedFeelib = PriceFeedFeelib::setArbitrumExtension($initPriceFeedFeelib, $newArbitrumExtension); + return test::set::equal( + $expectedPriceFeedFeelib, + $newPriceFeedFeelib + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::PriceFeedFeeLib::getNativePriceUsd, "Serde::PriceFeedFeeLib::getNativePriceUsd"]) + .tpush([Serde::PriceFeedFeeLib::getFirstThreeFields, "Serde::PriceFeedFeeLib::getFirstThreeFields"]) + .tpush([Serde::PriceFeedFeelib::getArbitrumExtension, "Serde::PriceFeedFeelib::getArbitrumExtension"]) + .tpush([Serde::PriceFeedFeelib::getOptimismExtension, "Serde::PriceFeedFeelib::getOptimismExtension"]) + .tpush([Serde::PriceFeedFeelib::deserializeInts, "Serde::PriceFeedFeelib::deserializeInts"]) + .tpush([Serde::PriceFeedFeeLib::deserialize, "Serde::PriceFeedFeeLib::deserialize"]) + .tpush([Serde::PriceFeedFeeLib::setFirstThreeFields, "Serde::PriceFeedFeeLib::setFirstThreeFields"]) + .tpush([Serde::PriceFeedFeeLib::setNativePriceUsd, "Serde::PriceFeedFeeLib::setNativePriceUsd"]) + .tpush([Serde::PriceFeedFeeLib::setPricesAndOptimismExtension, "Serde::PriceFeedFeeLib::setPricesAndOptimismExtension"]) + .tpush([Serde::PriceFeedFeeLib::setNativePriceUsdAndOptimismExtension, "Serde::PriceFeedFeeLib::setNativePriceUsdAndOptimismExtension"]) + .tpush([Serde::PriceFeedFeeLib::setArbitrumExtension, "Serde::PriceFeedFeeLib::setArbitrumExtension"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/tests/main.fc new file mode 100644 index 00000000..31fbe79a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/tests/main.fc @@ -0,0 +1,832 @@ +#include "../../../../../../tests/testMain.fc"; +#include "../../../../../funC++/classlib.fc"; +#include "../ulnSendWorkerV1.fc"; +#include "../../feeLibUtils.fc"; +#include "../../../../../funC++/stdlib.fc"; +#include "../../../../../funC++/utils.fc"; +#include "../../../../../../tests/mocks.fc"; + +slice _testName() { return "UlnSendWorkerFactory"; } + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +;;; ============================== CONSTS ============================= + +const int GAS_CONSUMED_MARGIN_BPS = 100; + +const int QUOTE_CRC16 = 107686; ;; CRC16XModem of 'ulnWorker::quote' +const int SIMPLE_QUOTE_CRC16 = 74619; ;; CRC16XModem of 'ulnWorker::simpleQuote' +const int TOO_MANY_INPUTS_CRC16 = 73838; ;; CRC16XModem of 'ulnWorker::tooManyInputs' +const int TOO_FEW_INPUTS_CRC16 = 102787; ;; CRC16XModem of 'ulnWorker::tooFewInputs' +const int SXITY_FOUR_OUTPUTS_CRC16 = 101406; ;; CRC16XModem of 'ulnWorker::sixtyFourOutputs' + +const int PUT_16_ITEMS_BEFORE_OOG_CRC16 = 114832; ;; CRC16XModem of 'ulnWorker::put16ItemsBeforeOOG' +const int PUT_32_ITEMS_BEFORE_OOG_CRC16 = 72523; ;; CRC16XModem of 'ulnWorker::put32ItemsBeforeOOG' +const int PUT_64_ITEMS_BEFORE_OOG_CRC16 = 124374; ;; CRC16XModem of 'ulnWorker::put64ItemsBeforeOOG' + +const int FIBONACCI_FOR_OOG_CRC16 = 94357; ;; CRC16XModem of 'ulnWorker::fibonacci' + +const int NESTED_RUN_VM_CRC16 = 108721; ;; CRC16XModem of 'nestedRunVM' + +;; this is an actual "magic number", which is the added gas usage of overheads for handling RUNVM i/o +const int OOG_GAS_OVERHEAD = 626; +const int NESTED_GAS_OVERHEAD = 550; +const int MIN_EXTRA_LIMIT_TO_HANDLE_NESTED_OOG = 1354; + +;;; ============================== HELPERS ============================ + +() printCodeDict() impure { + cell codeDict = my_code().begin_parse().preload_first_ref(); + int count = 0; + int exists = -1; + int key = -1; + do { + (key, slice val, exists) = udict_get_next?(codeDict, 19, key); + count += 1; + } until (exists == 0); + ~strdump("code dict length"); + ~dump(count); +} + +(int, int) getExitCode_gasConsumed(tuple retStack) { + return ( + retStack.int_at(retStack.tlen() - 2), + retStack.int_at(retStack.tlen() - 1) + ); +} + +tuple nestedRunVm(tuple inputs) impure method_id { + tuple innerInputs = inputs.tuple_at(0); + int methodIdCRC = inputs.int_at(1); + int nOutputs = inputs.int_at(2); + slice code = inputs.slice_at(3); + int gasLimit = inputs.int_at(4); + + return safePackedInputsRunVm(innerInputs, methodIdCRC, nOutputs, code, gasLimit); +} + +;;; ============================== TESTS ============================= + +;; These tests are meant to demonstrate the behavior of RUNVM, specifically +;; the wrapper we've written around it. We do the demonstration by running tests +;; that shows a list of promises that the logic of RUNVM fulfills. + +;; Promise 1: RUNVM actually runs the target function. If everything goes well, +;; returns [output, 0, gasConsumed] +;; output: the output of the target function, +;; exitCode: 0, +;; gasConsumed: the gas consumed by the target function. + +(int, slice) callWorker::success::basic(cell $args) impure { + tuple inputs = unsafeTuple([ + cl::nullObject(), + cl::nullObject(), + MOCK_LZ_SEND_WITH_ID(10) + ]); + + int initGas = get_gas_consumed(); + tuple retStack = safePackedInputsRunVm( + inputs, + QUOTE_CRC16, ;; CRC16XModem of 'quotePacked' + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + tuple ret = retStack.tuple_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + (exitCode == 0) + & (gasConsumed < DEFAULT_WORKER_QUOTE_GAS_LIMIT) + & (totalGasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + ); +} + +;; Promise 2: If the target function throws an error, +;; returns [0, exitCode, gasConsumed] +;; where exitCode will be the one thrown by the target function, +;; and the gasConsumed will be <= limit + overhead. + +(int, slice) callWorker::fail::throw(cell $args) impure { + tuple inputs = unsafeTuple([ + cl::nullObject(), + cl::nullObject(), + MOCK_LZ_SEND_WITH_ID(111) ;; magic number that makes 'quote' throw + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + QUOTE_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + ;; note-erfan: when there's no actual return value as it throws, the output seems to default to 0. + (output == 0) + & (exitCode == 1000) ;; hardcoded in the worker + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + & (totalGasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + ); +} + +;; Promise 3: If the target functions tries to use more gas than the limit passed to RUNVM parameters, +;; returns [gasConsumed, -14, gasConsumed] +;; where gasConsumed will be <= limit + overhead. +;; also, if there are other variables pushed to the stack before running out of gas, +;; they will be discarded silently. + +(int, slice) callWorker::fail::16ItemsBeforeOOG(cell $args) impure { + tuple inputs = unsafeTuple([0, 1, 2]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + PUT_16_ITEMS_BEFORE_OOG_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + ;; note-erfan: I think in this case the first item of the stack and the last one will both be the gas consumed + return test::shouldBeTrue( + (output == gasConsumed) + & (exitCode == -14) + ;; This line ensures the reported gas consumption from the VM is within a small margin of the gas limit + & (gasConsumed < ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (totalGasConsumed == DEFAULT_WORKER_QUOTE_GAS_LIMIT + OOG_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::32ItemsBeforeOOG(cell $args) impure { + tuple inputs = unsafeTuple([0, 1, 2]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + PUT_32_ITEMS_BEFORE_OOG_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + (output == gasConsumed) + & (exitCode == -14) + ;; This line ensures the reported gas consumption from the VM is within a small margin of the gas limit + & (gasConsumed < ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (totalGasConsumed == DEFAULT_WORKER_QUOTE_GAS_LIMIT + OOG_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::64ItemsBeforeOOG(cell $args) impure { + tuple inputs = unsafeTuple([0, 1, 2]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + PUT_64_ITEMS_BEFORE_OOG_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + (output == gasConsumed) + & (exitCode == -14) + ;; This line ensures the reported gas consumption from the VM is within a small margin of the gas limit + & (gasConsumed < ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (totalGasConsumed == DEFAULT_WORKER_QUOTE_GAS_LIMIT + OOG_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::fibonacciForOOG(cell $args) impure { + tuple inputs = unsafeTuple([20]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + FIBONACCI_FOR_OOG_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + (output == gasConsumed) + & (exitCode == -14) + ;; This line ensures the reported gas consumption from the VM is within a small margin of the gas limit + & (gasConsumed < ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (totalGasConsumed == DEFAULT_WORKER_QUOTE_GAS_LIMIT + OOG_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::fibonacciForOOG_2xGasLimit(cell $args) impure { + tuple inputs = unsafeTuple([22]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + FIBONACCI_FOR_OOG_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + ;; Assert the handler (1) ran out of gas and (2) the total gas used is equal to + ;; the gas limit within some tolerance (for fixed overheads of the helper function etc.) + return test::shouldBeTrue( + (output == gasConsumed) + & (exitCode == -14) + & (gasConsumed < ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (totalGasConsumed == DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 + OOG_GAS_OVERHEAD) + ); +} + +;; Promise 4: If the target function expects more inputs than provided, +;; returns [0, 2, gasConsumed] +;; where exitCode will be 2 (stack underflow), and the gasConsumed will be <= limit + overhead. + +(int, slice) callWorker::fail::expectsMoreInputs(cell $args) impure { + tuple inputs = unsafeTuple([0, 1, 2]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + TOO_MANY_INPUTS_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + (output == 0) + & (exitCode == 2) ;; stack underflow + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + ); +} + +;; Promise 5: If the target function expects less inputs than provided, +;; returns [output, 0, gasConsumed] +;; where exitCode will be 0, and the gasConsumed will be <= limit + overhead. +;; the expected number of inputs will be taken from the stack and the rest will be ignored. + +(int, slice) callWorker::success::expectsLessInputs(cell $args) impure { + tuple inputs = unsafeTuple([0, 1, 2]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + TOO_FEW_INPUTS_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + (output == 200) ;; hardcoded in the worker + & (exitCode == 0) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + ); +} + +;; Promise 6: If the target function returns more outputs than expected, (pushed more things to the stack than expected) +;; returns [output, 0, gasConsumed] +;; where output is the expected output, +;; exitCode is 0, +;; gasConsumed does not depend on the number of outputs, and will be <= limit + overhead. + +;; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +;; - These two tests are actually one test, but we had to split them up because of the VM limitations. + +(int, slice) callWorker::success::tooManyOutputs_ExactOutputs(cell $args) impure { + tuple inputs = unsafeTuple([0, 1, 2]); + + int initGas = get_gas_consumed(); + + ;; takes in all 64 outputs, doesn't drop anything. + tuple retStack = safePackedInputsRunVm( + inputs, + SXITY_FOUR_OUTPUTS_CRC16, + 64, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + (exitCode == 0) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + & (totalGasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + & (retStack.tlen() == 64 + 2) ;; 64 outputs + exit code + gas consumed + ); +} + +(int, slice) callWorker::success::tooManyOutputs_LessOutputs(cell $args) impure { + tuple inputs = unsafeTuple([0, 1, 2]); + + int initGas = get_gas_consumed(); + + ;; takes in 32 outputs, drops 32 outputs that were pushed after that. + tuple retStack = safePackedInputsRunVm( + inputs, + SXITY_FOUR_OUTPUTS_CRC16, + 32, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + (exitCode == 0) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + ;; because we need to pack 32 less inputs in a tuple here, so it costs less gas + & (totalGasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + & (retStack.tlen() == 32 + 2) ;; 32 outputs + exit code + gas consumed + ); +} +;; xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + + +;; Promise 7: Running a nested RUNVM does not break any of the promises we made about RUNVM. +;; if the target function in the worker bytecode returns more than the number of outputs specified, +;; the extra outputs are dropped silently and the gas consumed is not affected by the actual number of outputs + +(int, slice) callWorker::success::nested_basic(cell $args) impure { + tuple innerInputs = unsafeTuple([ + cl::nullObject(), + cl::nullObject(), + MOCK_LZ_SEND_WITH_ID(10) + ]); + + tuple inputs = unsafeTuple([ + innerInputs, + QUOTE_CRC16, + 1, + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + NESTED_RUN_VM_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + tuple nestedRetStack = retStack.tuple_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + (int nestedExitCode, int nestedGasConsumed) = getExitCode_gasConsumed(nestedRetStack); + + return test::shouldBeTrue( + ;; exit code checks + (exitCode == 0) + & (nestedExitCode == 0) + + ;; gas checks + & (nestedGasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2) + & (totalGasConsumed - gasConsumed == NESTED_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::nested_throw(cell $args) impure { + tuple innerInputs = unsafeTuple([ + cl::nullObject(), + cl::nullObject(), + MOCK_LZ_SEND_WITH_ID(111) + ]); + + tuple inputs = unsafeTuple([ + innerInputs, + QUOTE_CRC16, + 1, + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + NESTED_RUN_VM_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + tuple nestedRetStack = retStack.tuple_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + int nestedOutput = nestedRetStack.int_at(0); + (int nestedExitCode, int nestedGasConsumed) = getExitCode_gasConsumed(nestedRetStack); + + return test::shouldBeTrue( + ;; exit code checks + (exitCode == 0) + & (nestedExitCode == 1000) + + ;; output checks + & (nestedOutput == 0) + + ;; gas checks + & (nestedGasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2) + & (totalGasConsumed - gasConsumed == NESTED_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::nested_16ItemsBeforeOOG(cell $args) impure { + tuple inputs = unsafeTuple([ + unsafeTuple([0, 1, 2]), + PUT_16_ITEMS_BEFORE_OOG_CRC16, + 1, + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + NESTED_RUN_VM_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + tuple nestedRetStack = retStack.tuple_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + int nestedOutput = nestedRetStack.int_at(0); + (int nestedExitCode, int nestedGasConsumed) = getExitCode_gasConsumed(nestedRetStack); + + return test::shouldBeTrue( + ;; exit code checks + (exitCode == 0) + & (nestedExitCode == -14) + + ;; output checks + & (nestedOutput == nestedGasConsumed) + + ;; gas checks + ;; the nested OOG gas uses more gas than the limit because it has some overhead. + & (nestedGasConsumed <= ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2) + & (totalGasConsumed - gasConsumed == NESTED_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::nested_32ItemsBeforeOOG(cell $args) impure { + tuple inputs = unsafeTuple([ + unsafeTuple([0, 1, 2]), + PUT_32_ITEMS_BEFORE_OOG_CRC16, + 1, + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + NESTED_RUN_VM_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + tuple nestedRetStack = retStack.tuple_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + int nestedOutput = nestedRetStack.int_at(0); + (int nestedExitCode, int nestedGasConsumed) = getExitCode_gasConsumed(nestedRetStack); + + return test::shouldBeTrue( + ;; exit code checks + (exitCode == 0) + & (nestedExitCode == -14) + + ;; output checks + & (nestedOutput == nestedGasConsumed) + + ;; gas checks + ;; the nested OOG gas uses more gas than the limit because it has some overhead. + & (nestedGasConsumed <= ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2) + & (totalGasConsumed - gasConsumed == NESTED_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::nested_64ItemsBeforeOOG(cell $args) impure { + tuple inputs = unsafeTuple([ + unsafeTuple([0, 1, 2]), + PUT_64_ITEMS_BEFORE_OOG_CRC16, + 1, + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + NESTED_RUN_VM_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + tuple nestedRetStack = retStack.tuple_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + int nestedOutput = nestedRetStack.int_at(0); + (int nestedExitCode, int nestedGasConsumed) = getExitCode_gasConsumed(nestedRetStack); + + return test::shouldBeTrue( + ;; exit code checks + (exitCode == 0) + & (nestedExitCode == -14) + + ;; output checks + & (nestedOutput == nestedGasConsumed) + + ;; gas checks + ;; the nested OOG gas uses more gas than the limit because it has some overhead. + & (nestedGasConsumed <= ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2) + & (totalGasConsumed - gasConsumed == NESTED_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::nested_fibonacciForOOG(cell $args) impure { + tuple inputs = unsafeTuple([ + unsafeTuple([20]), + FIBONACCI_FOR_OOG_CRC16, + 1, + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + NESTED_RUN_VM_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + tuple nestedRetStack = retStack.tuple_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + int nestedOutput = nestedRetStack.int_at(0); + (int nestedExitCode, int nestedGasConsumed) = getExitCode_gasConsumed(nestedRetStack); + + return test::shouldBeTrue( + ;; exit code checks + (exitCode == 0) + & (nestedExitCode == -14) + + ;; output checks + & (nestedOutput == nestedGasConsumed) + + ;; gas checks + ;; the nested OOG gas uses more gas than the limit because it has some overhead. + & (nestedGasConsumed <= ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2) + & (totalGasConsumed - gasConsumed == NESTED_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::nested_fibonacciForOOG_2xGasLimit(cell $args) impure { + tuple inputs = unsafeTuple([ + unsafeTuple([22]), + FIBONACCI_FOR_OOG_CRC16, + 1, + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + NESTED_RUN_VM_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT * 3 ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + tuple nestedRetStack = retStack.tuple_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + int nestedOutput = nestedRetStack.int_at(0); + (int nestedExitCode, int nestedGasConsumed) = getExitCode_gasConsumed(nestedRetStack); + + return test::shouldBeTrue( + ;; exit code checks + (exitCode == 0) + & (nestedExitCode == -14) + + ;; output checks + & (nestedOutput == nestedGasConsumed) + + ;; gas checks + ;; the nested OOG gas uses more gas than the limit because it has some overhead. + & (nestedGasConsumed <= ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2 * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (gasConsumed <= DEFAULT_WORKER_QUOTE_GAS_LIMIT * 3) + & (totalGasConsumed - gasConsumed == NESTED_GAS_OVERHEAD) + ); +} + +;; Promise 8: If the child VM goes OOG, and the parent VM doesn't have enough gas limit +;; to handle it, the parent VM should just return its own OOG exit code and not revert. +(int, slice) callWorker::fail::OOG_inHandling_OOG_fibonacci(cell $args) impure { + tuple inputs = unsafeTuple([ + unsafeTuple([20]), + FIBONACCI_FOR_OOG_CRC16, + 1, + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + NESTED_RUN_VM_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + ;; exit code checks + (exitCode == -14) + + ;; output checks + & (output == gasConsumed) + + ;; gas checks + & (gasConsumed < ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (totalGasConsumed - gasConsumed == NESTED_GAS_OVERHEAD) + ); +} + +(int, slice) callWorker::fail::OOG_inHandling_OOG_64Items(cell $args) impure { + tuple inputs = unsafeTuple([ + unsafeTuple([0, 1, 2]), + PUT_64_ITEMS_BEFORE_OOG_CRC16, + 1, + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT + ]); + + int initGas = get_gas_consumed(); + + tuple retStack = safePackedInputsRunVm( + inputs, + NESTED_RUN_VM_CRC16, + 1, ;; nOutputs + my_code().begin_parse(), + DEFAULT_WORKER_QUOTE_GAS_LIMIT ;; gaslimit + ); + + int totalGasConsumed = get_gas_consumed() - initGas; + + int output = retStack.int_at(0); + (int exitCode, int gasConsumed) = getExitCode_gasConsumed(retStack); + + return test::shouldBeTrue( + ;; exit code checks + (exitCode == -14) + + ;; output checks + & (output == gasConsumed) + + ;; gas checks + & (gasConsumed < ((DEFAULT_WORKER_QUOTE_GAS_LIMIT * (10000 + GAS_CONSUMED_MARGIN_BPS)) / 10000)) + & (totalGasConsumed - gasConsumed == NESTED_GAS_OVERHEAD) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + ;; -- these are the normal RUNVM tests + .tpush([callWorker::success::basic, "callWorker::success::basic"]) + .tpush([callWorker::fail::throw, "callWorker::fail::throw"]) + + .tpush([callWorker::fail::16ItemsBeforeOOG, "callWorker::fail::16ItemsBeforeOOG"]) + .tpush([callWorker::fail::32ItemsBeforeOOG, "callWorker::fail::32ItemsBeforeOOG"]) + .tpush([callWorker::fail::64ItemsBeforeOOG, "callWorker::fail::64ItemsBeforeOOG"]) + + .tpush([callWorker::fail::fibonacciForOOG, "callWorker::fail::fibonacciForOOG"]) + .tpush([callWorker::fail::fibonacciForOOG_2xGasLimit, "callWorker::fail::fibonacciForOOG_2xGasLimit"]) + + .tpush([callWorker::fail::expectsMoreInputs, "callWorker::fail::expectsMoreInputs"]) + .tpush([callWorker::success::expectsLessInputs, "callWorker::success::expectsLessInputs"]) + + .tpush([callWorker::success::tooManyOutputs_ExactOutputs, "callWorker::success::tooManyOutputs_ExactOutputs"]) + .tpush([callWorker::success::tooManyOutputs_LessOutputs, "callWorker::success::tooManyOutputs_LessOutputs"]) + + ;; -- these are the nested RUNVM tests + .tpush([callWorker::success::nested_basic, "callWorker::success::nested_basic"]) + .tpush([callWorker::fail::nested_throw, "callWorker::fail::nested_throw"]) + + .tpush([callWorker::fail::nested_16ItemsBeforeOOG, "callWorker::fail::nested_16ItemsBeforeOOG"]) + .tpush([callWorker::fail::nested_32ItemsBeforeOOG, "callWorker::fail::nested_32ItemsBeforeOOG"]) + .tpush([callWorker::fail::nested_64ItemsBeforeOOG, "callWorker::fail::nested_64ItemsBeforeOOG"]) + + .tpush([callWorker::fail::nested_fibonacciForOOG, "callWorker::fail::nested_fibonacciForOOG"]) + .tpush([callWorker::fail::nested_fibonacciForOOG_2xGasLimit, "callWorker::fail::nested_fibonacciForOOG_2xGasLimit"]) + + .tpush([callWorker::fail::OOG_inHandling_OOG_fibonacci, "callWorker::fail::OOG_inHandling_OOG_fibonacci"]) + .tpush([callWorker::fail::OOG_inHandling_OOG_64Items, "callWorker::fail::OOG_inHandling_OOG_64Items"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/ulnSendWorkerV1.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/ulnSendWorkerV1.fc new file mode 100644 index 00000000..11dbeb5f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/protocol/msglibs/ultralightnode/workerFeeLibs/ulnSendWorkerV1.fc @@ -0,0 +1,363 @@ +#include "../../../../funC++/classlib.fc"; +#include "../../../../funC++/stdlib.fc"; +#include "../../../../funC++/utils.fc"; +#include "../../../../classes/msgdata/LzSend.fc"; +;; updated storage +cell ulnWorker::setConfig(cell $storage, cell $md) method_id { + return $storage; +} + +tuple ulnWorker::quote(tuple inputs) method_id { + cell $lzSend = inputs.cell_at(2); + + int maxCount = $lzSend.cl::get(md::LzSend::sendRequestId); + throw_if(1000, maxCount == 111); + + int count = 0; + while (count < maxCount) { + count += 1; + } + + return unsafeTuple( + [42, begin_cell().store_uint256(42).end_cell()] + ); +} + +tuple ulnWorker::simpleQuote(tuple inputs) method_id { + return inputs; +} + +int ulnWorker::tooManyInputs(tuple one, tuple two, tuple three) method_id { + return 100; +} + +int ulnWorker::tooFewInputs() method_id { + return 200; +} + +(int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int) ulnWorker::sixtyFourOutputs(tuple inputs) method_id { + return (1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); +} + +int ulnWorker::put16ItemsBeforeOOG(tuple inputs) method_id { + int a = 1; + int b = 2; + int c = 3; + int d = 4; + int e = 5; + int f = 6; + int g = 7; + int h = 8; + int i = 9; + int j = 10; + int k = 11; + int l = 12; + int m = 13; + int n = 14; + int o = 15; + int p = 16; + + int counter = 10 * 1000; + + while (counter > 0) { + counter -= 1; + } + + return (a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p); +} + +int ulnWorker::put32ItemsBeforeOOG(tuple inputs) method_id { + int a0 = 100; + int a1 = 101; + int a2 = 102; + int a3 = 103; + int a4 = 104; + int a5 = 105; + int a6 = 106; + int a7 = 107; + int a8 = 108; + int a9 = 109; + int a10 = 110; + int a11 = 111; + int a12 = 112; + int a13 = 113; + int a14 = 114; + int a15 = 115; + int a16 = 116; + int a17 = 117; + int a18 = 118; + int a19 = 119; + int a20 = 120; + int a21 = 121; + int a22 = 122; + int a23 = 123; + int a24 = 124; + int a25 = 125; + int a26 = 126; + int a27 = 127; + int a28 = 128; + int a29 = 129; + int a30 = 130; + int a31 = 131; + int a32 = 132; + + int counter = 20 * 1000; + + while (counter > 0) { + counter -= 1; + } + + return (a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16 + a17 + a18 + a19 + a20 + a21 + a22 + a23 + a24 + a25 + a26 + a27 + a28 + a29 + a30 + a31 + a32); +} + +int ulnWorker::put64ItemsBeforeOOG(tuple inputs) method_id { + cell a0 = begin_cell().store_uint(100, 64).end_cell(); + cell a1 = begin_cell().store_uint(101, 64).end_cell(); + cell a2 = begin_cell().store_uint(102, 64).end_cell(); + cell a3 = begin_cell().store_uint(103, 64).end_cell(); + cell a4 = begin_cell().store_uint(104, 64).end_cell(); + cell a5 = begin_cell().store_uint(105, 64).end_cell(); + cell a6 = begin_cell().store_uint(106, 64).end_cell(); + cell a7 = begin_cell().store_uint(107, 64).end_cell(); + cell a8 = begin_cell().store_uint(108, 64).end_cell(); + cell a9 = begin_cell().store_uint(109, 64).end_cell(); + cell a10 = begin_cell().store_uint(110, 64).end_cell(); + cell a11 = begin_cell().store_uint(111, 64).end_cell(); + cell a12 = begin_cell().store_uint(112, 64).end_cell(); + cell a13 = begin_cell().store_uint(113, 64).end_cell(); + cell a14 = begin_cell().store_uint(114, 64).end_cell(); + cell a15 = begin_cell().store_uint(115, 64).end_cell(); + cell a16 = begin_cell().store_uint(116, 64).end_cell(); + cell a17 = begin_cell().store_uint(117, 64).end_cell(); + cell a18 = begin_cell().store_uint(118, 64).end_cell(); + cell a19 = begin_cell().store_uint(119, 64).end_cell(); + cell a20 = begin_cell().store_uint(120, 64).end_cell(); + cell a21 = begin_cell().store_uint(121, 64).end_cell(); + cell a22 = begin_cell().store_uint(122, 64).end_cell(); + cell a23 = begin_cell().store_uint(123, 64).end_cell(); + cell a24 = begin_cell().store_uint(124, 64).end_cell(); + cell a25 = begin_cell().store_uint(125, 64).end_cell(); + cell a26 = begin_cell().store_uint(126, 64).end_cell(); + cell a27 = begin_cell().store_uint(127, 64).end_cell(); + cell a28 = begin_cell().store_uint(128, 64).end_cell(); + cell a29 = begin_cell().store_uint(129, 64).end_cell(); + cell a30 = begin_cell().store_uint(130, 64).end_cell(); + cell a31 = begin_cell().store_uint(131, 64).end_cell(); + cell a32 = begin_cell().store_uint(132, 64).end_cell(); + cell a33 = begin_cell().store_uint(133, 64).end_cell(); + cell a34 = begin_cell().store_uint(134, 64).end_cell(); + cell a35 = begin_cell().store_uint(135, 64).end_cell(); + cell a36 = begin_cell().store_uint(136, 64).end_cell(); + cell a37 = begin_cell().store_uint(137, 64).end_cell(); + cell a38 = begin_cell().store_uint(138, 64).end_cell(); + cell a39 = begin_cell().store_uint(139, 64).end_cell(); + cell a40 = begin_cell().store_uint(140, 64).end_cell(); + cell a41 = begin_cell().store_uint(141, 64).end_cell(); + cell a42 = begin_cell().store_uint(142, 64).end_cell(); + cell a43 = begin_cell().store_uint(143, 64).end_cell(); + cell a44 = begin_cell().store_uint(144, 64).end_cell(); + cell a45 = begin_cell().store_uint(145, 64).end_cell(); + cell a46 = begin_cell().store_uint(146, 64).end_cell(); + cell a47 = begin_cell().store_uint(147, 64).end_cell(); + cell a48 = begin_cell().store_uint(148, 64).end_cell(); + cell a49 = begin_cell().store_uint(149, 64).end_cell(); + cell a50 = begin_cell().store_uint(150, 64).end_cell(); + cell a51 = begin_cell().store_uint(151, 64).end_cell(); + cell a52 = begin_cell().store_uint(152, 64).end_cell(); + cell a53 = begin_cell().store_uint(153, 64).end_cell(); + cell a54 = begin_cell().store_uint(154, 64).end_cell(); + cell a55 = begin_cell().store_uint(155, 64).end_cell(); + cell a56 = begin_cell().store_uint(156, 64).end_cell(); + cell a57 = begin_cell().store_uint(157, 64).end_cell(); + cell a58 = begin_cell().store_uint(158, 64).end_cell(); + cell a59 = begin_cell().store_uint(159, 64).end_cell(); + cell a60 = begin_cell().store_uint(160, 64).end_cell(); + cell a61 = begin_cell().store_uint(161, 64).end_cell(); + cell a62 = begin_cell().store_uint(162, 64).end_cell(); + cell a63 = begin_cell().store_uint(163, 64).end_cell(); + cell a64 = begin_cell().store_uint(164, 64).end_cell(); + + int counter = 30 * 1000; + + while (counter > 0) { + counter -= 1; + } + + return ( + a0.begin_parse().preload_uint(64) + + a1.begin_parse().preload_uint(64) + + a2.begin_parse().preload_uint(64) + + a3.begin_parse().preload_uint(64) + + a4.begin_parse().preload_uint(64) + + a5.begin_parse().preload_uint(64) + + a6.begin_parse().preload_uint(64) + + a7.begin_parse().preload_uint(64) + + a8.begin_parse().preload_uint(64) + + a9.begin_parse().preload_uint(64) + + a10.begin_parse().preload_uint(64) + + a11.begin_parse().preload_uint(64) + + a12.begin_parse().preload_uint(64) + + a13.begin_parse().preload_uint(64) + + a14.begin_parse().preload_uint(64) + + a15.begin_parse().preload_uint(64) + + a16.begin_parse().preload_uint(64) + + a17.begin_parse().preload_uint(64) + + a18.begin_parse().preload_uint(64) + + a19.begin_parse().preload_uint(64) + + a20.begin_parse().preload_uint(64) + + a21.begin_parse().preload_uint(64) + + a22.begin_parse().preload_uint(64) + + a23.begin_parse().preload_uint(64) + + a24.begin_parse().preload_uint(64) + + a25.begin_parse().preload_uint(64) + + a26.begin_parse().preload_uint(64) + + a27.begin_parse().preload_uint(64) + + a28.begin_parse().preload_uint(64) + + a29.begin_parse().preload_uint(64) + + a30.begin_parse().preload_uint(64) + + a31.begin_parse().preload_uint(64) + + a32.begin_parse().preload_uint(64) + + a33.begin_parse().preload_uint(64) + + a34.begin_parse().preload_uint(64) + + a35.begin_parse().preload_uint(64) + + a36.begin_parse().preload_uint(64) + + a37.begin_parse().preload_uint(64) + + a38.begin_parse().preload_uint(64) + + a39.begin_parse().preload_uint(64) + + a40.begin_parse().preload_uint(64) + + a41.begin_parse().preload_uint(64) + + a42.begin_parse().preload_uint(64) + + a43.begin_parse().preload_uint(64) + + a44.begin_parse().preload_uint(64) + + a45.begin_parse().preload_uint(64) + + a46.begin_parse().preload_uint(64) + + a47.begin_parse().preload_uint(64) + + a48.begin_parse().preload_uint(64) + + a49.begin_parse().preload_uint(64) + + a50.begin_parse().preload_uint(64) + + a51.begin_parse().preload_uint(64) + + a52.begin_parse().preload_uint(64) + + a53.begin_parse().preload_uint(64) + + a54.begin_parse().preload_uint(64) + + a55.begin_parse().preload_uint(64) + + a56.begin_parse().preload_uint(64) + + a57.begin_parse().preload_uint(64) + + a58.begin_parse().preload_uint(64) + + a59.begin_parse().preload_uint(64) + + a60.begin_parse().preload_uint(64) + + a61.begin_parse().preload_uint(64) + + a62.begin_parse().preload_uint(64) + + a63.begin_parse().preload_uint(64) + + a64.begin_parse().preload_uint(64) + ); +} + +;; this is the other case, we could run out of gas by stack overflowing or by the gas limit +int ulnWorker::fibonacci(tuple inputs) method_id { + int n = inputs.int_at(0); + + if (n <= 0) { + return 0; + } + if (n == 1) { + return 1; + } + return ulnWorker::fibonacci(unsafeTuple([n - 1])) + ulnWorker::fibonacci(unsafeTuple([n - 2])); +} + +int ulnWorker::tooFewInputs1() method_id { + return 200; +} +int ulnWorker::tooFewInputs2() method_id { + return 200; +} +int ulnWorker::tooFewInputs3() method_id { + return 200; +} +int ulnWorker::tooFewInputs4() method_id { + return 200; +} +int ulnWorker::tooFewInputs5() method_id { + return 200; +} +int ulnWorker::tooFewInputs6() method_id { + return 200; +} +int ulnWorker::tooFewInputs7() method_id { + return 200; +} +int ulnWorker::tooFewInputs8() method_id { + return 200; +} +int ulnWorker::tooFewInputs9() method_id { + return 200; +} +int ulnWorker::tooFewInputs10() method_id { + return 200; +} +int ulnWorker::tooFewInputs11() method_id { + return 200; +} +int ulnWorker::tooFewInputs12() method_id { + return 200; +} +int ulnWorker::tooFewInputs13() method_id { + return 200; +} +int ulnWorker::tooFewInputs14() method_id { + return 200; +} +int ulnWorker::tooFewInputs15() method_id { + return 200; +} +int ulnWorker::tooFewInputs16() method_id { + return 200; +} +int ulnWorker::tooFewInputs17() method_id { + return 200; +} +int ulnWorker::tooFewInputs18() method_id { + return 200; +} +int ulnWorker::tooFewInputs19() method_id { + return 200; +} +int ulnWorker::tooFewInputs20() method_id { + return 200; +} +int ulnWorker::tooFewInputs21() method_id { + return 200; +} +int ulnWorker::tooFewInputs22() method_id { + return 200; +} +int ulnWorker::tooFewInputs23() method_id { + return 200; +} +int ulnWorker::tooFewInputs24() method_id { + return 200; +} +int ulnWorker::tooFewInputs25() method_id { + return 200; +} +int ulnWorker::tooFewInputs26() method_id { + return 200; +} +int ulnWorker::tooFewInputs27() method_id { + return 200; +} +int ulnWorker::tooFewInputs28() method_id { + return 200; +} +int ulnWorker::tooFewInputs29() method_id { + return 200; +} +int ulnWorker::tooFewInputs30() method_id { + return 200; +} +int ulnWorker::tooFewInputs31() method_id { + return 200; +} +int ulnWorker::tooFewInputs32() method_id { + return 200; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/abstract/workerHandler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/abstract/workerHandler.fc new file mode 100644 index 00000000..551ffc7c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/abstract/workerHandler.fc @@ -0,0 +1,202 @@ +#include "../interface.fc"; +#include "../../../funC++/handlerCore.fc"; + +#include "../../../funC++/actions/call.fc"; +#include "../../../funC++/actions/event.fc"; +#include "../../../funC++/actions/payment.fc"; +#include "../../../funC++/actions/dispatch.fc"; + +#include "../../../classes/msgdata/CoinsAmount.fc"; +#include "../../../classes/msgdata/SetAddress.fc"; +#include "../../../classes/msgdata/MdObj.fc"; + +#include "../../msgdata/ExecuteParams.fc"; +#include "../../msgdata/ClaimTon.fc"; + +#include "../../../protocol/msglibs/ultralightnode/uln/interface.fc"; +#include "../../../protocol/msglibs/ultralightnode/ulnManager/interface.fc"; +#include "../../proxy/interface.fc"; +#include "../workerCoreStorage.fc"; +#include "../../../funC++/utils.fc"; + +cell getInitialStorage() impure inline { + return empty_cell(); +} + +;; declared inside of the actions/event.fc +;; We declare it here because it saves the need for declaring initialStorage everytime we call event +tuple _newAction(int topic, cell $body) impure inline { + return action::event::create(topic, $body, cl::nullObject()); +} + +;;; ========================================== +;; Modifiers +() assertAuthenticated() impure inline { } + +() assertInitialized() impure inline { } + +() assertAdmin() impure inline { + throw_unless( + Worker::ERROR::onlyAdmin, + AddressList::includes( + getCaller(), + getAdmins().begin_parse() + ) + ); +} + +;; Step 1: authenticate +() authenticate() impure { } + +() authenticateIfNecessary() impure inline { } + +;; Step 2: initialize +;; mdAddress: (md=null, address=proxy), they're both 256. +tuple initialize(cell $mdAddress) impure inline { + return emptyActions(); +} + +() assertOwner() impure inline { } + +;;; =================================== EVENT FUNCTIONS ================================= + +int _getEventSink() impure inline method_id { + int eventSink = getProxyAddress(); + if (eventSink == NULLADDRESS) { + return getContractAddress(); + } + return eventSink; +} + +int verifyEventOrigin(int address) method_id { + return AddressList::includes( + address, getAdmins().begin_parse() + ); +} + +;;; =================================== (HANDLER) HELPER FUNCTIONS ================================= + +const int ONE_TON = 1000000000; ;; 1 ton is equivalent to 10^9 nanotons + +tuple _claimTon(tuple actions, cell $claimTon) impure { + + int contractBalance = getInitialContractBalance(); + int claimAmount = $claimTon.cl::get(md::ClaimTon::amount); + + throw_unless( + Worker::ERROR::insufficientBalance, + (contractBalance >= (claimAmount + ONE_TON)) + ); + + ;; this amount will be paid from the contract balance + actions~pushAction( + $claimTon.cl::get
(md::ClaimTon::target), + claimAmount, + claimAmount + ); + + actions~pushAction( + Worker::event::CLAIMED_TON, + $claimTon + ); + + return actions; +} + +tuple _claimTonFromProxy(tuple actions, cell $claimTon) impure { + ;; event needs to be emitted before the call because call is a terminal action + actions~pushAction( + Worker::event::CLAIMED_TON_FROM_PROXY, + $claimTon + ); + + actions~pushAction( + getProxyAddress(), + Worker::OP::CLAIM_TON, + $claimTon + ); + + return actions; +} + +tuple _callContract(tuple actions, cell $executeParams) impure inline { + (int target, int opcode, cell callData) = $executeParams.md::ExecuteParams::getActionArgs(); + + actions~pushAction( + target, + opcode, + callData + ); + + return actions; +} + +tuple _setAdmins(tuple actions, cell $addressList) impure { + actions~pushAction( + Worker::event::ADMINS_SET, + $addressList + ); + + setContractStorage( + getContractStorage().setCoreStorage( + getCoreStorage().cl::set( + WorkerCoreStorage::admins, + $addressList + ) + ) + ); + + return actions; +} + +tuple _setProxyAddress(tuple actions, int newProxyAddress) impure { + setContractStorage( + getContractStorage().setCoreStorage( + getCoreStorage().cl::set(WorkerCoreStorage::proxy, newProxyAddress) + ) + ); + return actions; +} + +tuple _callViaProxy(tuple actions, cell $executeParams) impure { + ;; only these opcodes are allowed to be called from the worker to the proxy + int opcode = $executeParams.md::ExecuteParams::getOpcode(); + + throw_unless( + Worker::ERROR::invalidSignedOpcode, + (opcode == UlnManager::OP::ADD_ULN_WORKER) ;; ask the UlnManager to add this worker to ULN + | (opcode == Uln::OP::DEREGISTER_WORKER_FEELIB) ;; ask the ULN to remove this worker + | (opcode == Uln::OP::SET_WORKER_FEELIB_STORAGE) ;; ask the ULN to update this worker's storage + ); + + actions~pushAction( + getProxyAddress(), + Proxy::OP::CALL_CONTRACT, + $executeParams + ); + + return actions; +} + +tuple _emitViaProxy(tuple actions, int topic, cell $body) impure { + actions~pushAction( + getProxyAddress(), + Proxy::OP::EMIT_EVENT, + action::event::build(topic, $body, cl::nullObject()), + _gasToNanoton(PROXY_CALL_DEFAULT_GAS) + ); + + return actions; +} + +;;; =================================== VIEW FUNCTIONS ================================= + +int getLatestAdmin() impure method_id { + slice adminSlice = getAdmins().begin_parse(); + return adminSlice~AddressList::next(); +} + +int getClaimableTonView(int futureSeconds) impure inline { + int ret = getContractBalanceView(futureSeconds) - ONE_TON; + return max(0, ret); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/interface.fc new file mode 100644 index 00000000..d248c45e --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/interface.fc @@ -0,0 +1,31 @@ +;;; ==========================OPCODES===================================== +;; Any authorized signer can call CLAIM_TON to claim funds in the executor contract. +const int Worker::OP::CLAIM_TON = "Worker::OP::CLAIM_TON"c; +const int Worker::OP::CLAIM_TON_FROM_PROXY = "Worker::OP::CLAIM_TON_FROM_PROXY"c; + +;; Send an arbitrary command to an arbitrary contract +;; only owner +const int Worker::OP::CALL_VIA_PROXY = "Worker::OP::CALL_VIA_PROXY"c; + +;; Change the proxy address +;; only admins +const int Worker::OP::SET_PROXY = "Worker::OP::SET_PROXY"c; + +;; Change the list of admins +;; only admins +const int Worker::OP::SET_ADMINS = "Worker::OP::SET_ADMINS"c; + +;;; ==========================ERRORS===================================== +const int Worker::ERROR::invalidForwardingAddress = 2016; +const int Worker::ERROR::onlyAdmin = 2015; +const int Worker::ERROR::invalidOpcode = 2014; + +const int Worker::ERROR::invalidSignedOpcode = 2013; +const int Worker::ERROR::invalidTarget = 2012; +const int Worker::ERROR::insufficientBalance = 2011; +;;; ==========================EVENTS===================================== +const int Worker::event::ADMINS_SET = "Worker::event::ADMINS_SET"u; +const int Worker::event::CLAIMED_TON = "Worker::event::CLAIMED_TON"u; +const int Worker::event::CLAIMED_TON_FROM_PROXY = "Worker::event::CLAIMED_TON_PROXY"u; + +const int PROXY_CALL_DEFAULT_GAS = 1000000; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/dummyHandler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/dummyHandler.fc new file mode 100644 index 00000000..87742dee --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/dummyHandler.fc @@ -0,0 +1,45 @@ +;;; ====================================== +;; This "handler" is a mock to allow testing of an abstract contract +;;; ====================================== +#include "../../../funC++/actions/utils.fc"; + +#include "../../executor/storage.fc"; +#include "../interface.fc"; + +#include "../../../classes/msgdata/SetAddress.fc"; + +#include "../abstract/workerHandler.fc"; + +(cell, tuple) _initialize(cell $initEndpoint) impure inline { + return (getContractStorage(), emptyActions()); +} + +;;; ================PERMISSION FUNCTIONS===================== +tuple claimTon(cell $claimTon) { + return _claimTon(emptyActions(), $claimTon); +} + +tuple callContract(cell $executeParams) { + return _callContract(emptyActions(), $executeParams); +} + +tuple setAdmins(cell $addressList) { + return _setAdmins(emptyActions(), $addressList); +} + +tuple setProxyAddress(cell $setAddress) { + return _setProxyAddress( + emptyActions(), + $setAddress.cl::get
(md::SetAddress::address) + ); +} + +tuple callViaProxy(cell $executeParams) { + return _callViaProxy(emptyActions(), $executeParams); +} + +tuple claimTonFromProxy(cell $claimTon) { + return _claimTonFromProxy(emptyActions(), $claimTon); +} + +() _checkPermissions(int op, cell $md) impure inline { } \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/main.fc new file mode 100644 index 00000000..9f317ac6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/main.fc @@ -0,0 +1,220 @@ +#include "../../../../tests/baseContractTest.fc"; +#include "workerStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/handlerCore.fc"; + +#include "../../../../tests/consts.fc"; + +#include "../interface.fc"; +#include "../abstract/workerHandler.fc"; +#include "../workerCoreStorage.fc"; + +#include "../../../classes/msgdata/MdAddress.fc"; + +#include "dummyHandler.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "workCore"; } + +;; A mock storage to allow testing the abstract worker core contract +cell MockWorker::New() impure { + cell admins = AddressList::serialize( + unsafeTuple([ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS]) + ); + + return cl::declare( + "MockWorker"u, + unsafeTuple([ + [cl::t::objRef, WorkerCoreStorage::New(admins, PROXY_ADDRESS, 0)] + ]) + ); +} + +cell createContractStorage() impure { + setContractStorage(MockWorker::New()); + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) claimTon::success::basic() impure { + return test::handler::shouldPass( + claimTon, + md::ClaimTon::New(MOCK_CLAIM_AMOUNT, ARBITRARY_ADDRESS), + unsafeTuple([ + MOCK_CLAIM_AMOUNT, + _newAction( + ARBITRARY_ADDRESS, + MOCK_CLAIM_AMOUNT, + MOCK_CLAIM_AMOUNT + ), + _newAction( + Worker::event::CLAIMED_TON, + md::ClaimTon::New(MOCK_CLAIM_AMOUNT, ARBITRARY_ADDRESS) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) claimTon::revert::lessThanOneTonRemaining() impure { + int claimAmount = getInitialContractBalance() - ONE_TON + 1; + return test::handler::shouldFail( + claimTon, + md::ClaimTon::New(claimAmount, ARBITRARY_ADDRESS), + Worker::ERROR::insufficientBalance + ); +} + +(int, slice) claimTonFromProxy::success::basic() impure { + cell $claimTon = md::ClaimTon::New(MOCK_CLAIM_AMOUNT, ARBITRARY_ADDRESS); + return test::handler::shouldPass( + claimTonFromProxy, + $claimTon, + unsafeTuple([ + 0, + _newAction( + Worker::event::CLAIMED_TON_FROM_PROXY, + $claimTon + ), + _newAction( + PROXY_ADDRESS, + Worker::OP::CLAIM_TON, + $claimTon + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) setAdmins::success::basic() impure { + cell $admins = AddressList::serialize(unsafeTuple( + [getCaller(), ARBITRARY_ADDRESS] + )); + + return test::handler::shouldPass( + setAdmins, + $admins, + unsafeTuple([ + 0, + _newAction( + Worker::event::ADMINS_SET, + $admins + ) + ]), + getContractStorage().setCoreStorage( + getCoreStorage().cl::set( + WorkerCoreStorage::admins, + $admins + ) + ), + txnContext + ); +} + +(int, slice) setProxyAddress:success::basic() impure { + return test::handler::shouldPass( + setProxyAddress, + md::SetAddress::New(PROXY_ADDRESS), + emptyActions(), + getContractStorage().setCoreStorage( + getCoreStorage().cl::set(WorkerCoreStorage::proxy, PROXY_ADDRESS) + ), + txnContext + ); +} + +(int, slice) callViaProxy::success::setWorkerFeelibStorage() impure { + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + MOCK_EXTRA_DATA(), + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ); + + return test::handler::shouldPass( + callViaProxy, + $executeParams, + unsafeTuple([ + 0, + _newAction( + PROXY_ADDRESS, + Proxy::OP::CALL_CONTRACT, + $executeParams + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) callViaProxy::success::deregisterWorkerFeeLib() impure { + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + MOCK_EXTRA_DATA(), + 0, + Uln::OP::DEREGISTER_WORKER_FEELIB, + NULLADDRESS + ); + + return test::handler::shouldPass( + callViaProxy, + $executeParams, + unsafeTuple([ + 0, + _newAction( + PROXY_ADDRESS, + Proxy::OP::CALL_CONTRACT, + $executeParams + ) + ]), + getContractStorage(), + txnContext + ); +} + + +(int, slice) callViaProxy::fail::randomOpcode() impure { + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + MOCK_EXTRA_DATA(), + 0, + OP::RANDOM, + NULLADDRESS + ); + + return test::handler::shouldFail( + callViaProxy, + $executeParams, + Worker::ERROR::invalidSignedOpcode + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + .tpush([claimTon::success::basic, "claimTon::success::basic"]) + .tpush([claimTon::revert::lessThanOneTonRemaining, "claimTon::revert::lessThanOneTonRemaining"]) + .tpush([claimTonFromProxy::success::basic, "claimTonFromProxy::success::basic"]) + .tpush([setAdmins::success::basic, "setAdmins::success::basic"]) + .tpush([setProxyAddress:success::basic, "setProxyAddress:success::basic"]) + .tpush([callViaProxy::success::setWorkerFeelibStorage, "callViaProxy::success::setWorkerFeelibStorage"]) + .tpush([callViaProxy::success::deregisterWorkerFeeLib, "callViaProxy::success::deregisterWorkerFeeLib"]) + .tpush([callViaProxy::fail::randomOpcode, "callViaProxy::fail::randomOpcode"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/serde.fc new file mode 100644 index 00000000..5e2433c9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/serde.fc @@ -0,0 +1,37 @@ +#include "../workerCoreStorage.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/utils.fc"; + +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "WorkerCore::Serde"; } + +;; WorkerCore: Has 2 getters, +(int, slice) Serde::WorkerCoreStorage::getAdmins(cell $unused) impure { + cell $storage = WorkerCoreStorage::New(MOCK_ADMIN_WORKER_LIST(), PROXY_ADDRESS, 1); + return test::getRef::equal( + $storage, + WorkerCoreStorage::getAdmins, + WorkerCoreStorage::admins + ); +} + +(int, slice) Serde::WorkerCoreStorage::getProxy(cell $unused) impure { + cell $storage = WorkerCoreStorage::New(MOCK_ADMIN_WORKER_LIST(), PROXY_ADDRESS, 1); + return test::getData::equal( + $storage, + WorkerCoreStorage::getProxy, + WorkerCoreStorage::proxy + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::WorkerCoreStorage::getAdmins, "Serde::WorkerCoreStorage::getAdmins"]) + .tpush([Serde::WorkerCoreStorage::getProxy, "Serde::WorkerCoreStorage::getProxy"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/workerStorageTestUtils.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/workerStorageTestUtils.fc new file mode 100644 index 00000000..3a9d6210 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/tests/workerStorageTestUtils.fc @@ -0,0 +1,11 @@ +#include "../workerCoreStorage.fc"; +#include "../../../funC++/stringlib.fc"; +#include "../../../funC++/classlib.fc"; + +() forceAuthenticate(int base_storage_idx) impure { } + +cell createContractStorage() impure; + +cell createInitializedStorage() impure { + return createContractStorage(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/workerCoreStorage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/workerCoreStorage.fc new file mode 100644 index 00000000..74244e20 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/core/workerCoreStorage.fc @@ -0,0 +1,59 @@ +#include "../../funC++/classlib.fc"; + +#include "interface.fc"; + +;; !!! If you put this storage anywhere other than index 0 of your custom contract storage, +;; you are gunna have a bad time +const int WORKER_CORE_STORAGE_IDX = 0; + +;; required object name +const int WorkerCoreStorage::NAME = "wrkCorStor"u; + +;; field names +const int WorkerCoreStorage::admins = 0; +const int WorkerCoreStorage::proxy = 1; +const int WorkerCoreStorage::version = 2; + +;; In all blockchains with atomic cross-contract call, we can use src/dst/sender/receiver +;; because the send channel doesn't exist (it's just a nonce). +;; In TON, we need both send/receive channels, so we use local/remote to provide +;; a context-free way to refer to the two ends of the channel. +;; The direction is inferred by the context of the contract (send vs receive). +;; The srcOApp is the 256-bit hashpart of a standard address. +cell WorkerCoreStorage::New(cell admins, int proxy, int version) impure inline method_id { + return cl::declare( + WorkerCoreStorage::NAME, + unsafeTuple([ + [cl::t::addressList, admins], ;; WorkerCoreStorage::admins + [cl::t::address, proxy], ;; WorkerCoreStorage::proxy + [cl::t::uint256, version] ;; WorkerCoreStorage::version + ]) + ); +} + +cell getCoreStorage() impure inline { + return getContractStorage().cl::get(WORKER_CORE_STORAGE_IDX); +} + +cell setCoreStorage(cell $storage, cell $newCoreStorage) impure inline { + return $storage.cl::set(WORKER_CORE_STORAGE_IDX, $newCoreStorage); +} + +const int WorkerCoreStorage::_proxyOffset = _HEADER_WIDTH; +const int WorkerCoreStorage::_versionOffset = WorkerCoreStorage::_proxyOffset + 256; + +cell WorkerCoreStorage::getAdmins(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} + +int WorkerCoreStorage::getProxy(cell $self) impure inline { + return $self.cellPreloadAddressAt(WorkerCoreStorage::_proxyOffset); +} + +int getProxyAddress() impure inline { + return getCoreStorage().WorkerCoreStorage::getProxy(); +} + +cell getAdmins() impure inline { + return getCoreStorage().WorkerCoreStorage::getAdmins(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/handler.fc new file mode 100644 index 00000000..654b193b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/handler.fc @@ -0,0 +1,405 @@ +#include "../core/interface.fc"; + +#include "../../funC++/actions/call.fc"; +#include "../../funC++/actions/event.fc"; + +#include "../../classes/msgdata/SetAddress.fc"; + +#include "../msgdata/SignedRequest.fc"; +#include "../msgdata/SetQuorum.fc"; +#include "../msgdata/SetDict.fc"; +#include "../msgdata/ExecuteParams.fc"; + +#include "../core/abstract/workerHandler.fc"; +#include "../../classes/msgdata/MdAddress.fc"; +#include "../proxy/interface.fc"; +#include "../../protocol/msglibs/ultralightnode/callbackOpcodes.fc"; +#include "../../protocol/msglibs/ultralightnode/msgdata/UlnVerification.fc"; +#include "../../protocol/msglibs/ultralightnode/uln/interface.fc"; + +#include "interface.fc"; +#include "storage.fc"; + +;;; Recovers public key from signature, identical to Bitcoin/Ethereum operations. +;;; Takes 32-byte hash as uint256 hash; 65-byte signature as [uint1 v, uint256 r, uint256 s]. +;;; Returns (null, null, null, 0) on failure, (publickey, -1) on success. public key is returned as [uint8 h, uint256 x1, uint256 x2]. +;;; In the SEC (Standards for Efficient Cryptography) format, uncompressed public keys start with 0x04, +;;; followed by the x and y coordinates of the point on the elliptic curve. +;;; This prefix distinguishes uncompressed keys from compressed ones (which use 0x02 or 0x03). +(int, int, int, int) ecrecover(int hash, int v, int r, int s) asm "ECRECOVER NULLSWAPIFNOT NULLSWAPIFNOT2"; + +;;; Decodes a 65-byte signature into uint8 v, uint256 r, uint256 s +(int, int, int) decodeSignature(cell signature) impure inline method_id { + slice signatureSlice = signature.begin_parse(); + + ;; Load r (first 32 bytes / 256 bits) = the x-coordinate of a point on the elliptic curve. + int r = signatureSlice~load_uint(256); + + ;; Load s (next 32 bytes / 256 bits) = the y-coordinate of a point on the elliptic curve. + int s = signatureSlice~load_uint(256); + + ;; Load v (last byte / 8 bits) = the recovery ID. + int v = signatureSlice~load_uint(8); + + ;; Adjust the 'v' value from 27/28 to 0/1 for passing to ecrecover. + return (v >= 27 ? v - 27 : v, r, s); +} + +;;; Encodes [uint256 x1, uint256 x2] into a cell which represents the 64-byte public key +cell encodePublicKey(int x1, int x2) impure inline method_id { + return begin_cell() + .store_uint256(x1) + .store_uint256(x2) + .end_cell(); +} + +;;; Verifies signatures on a dict256 of signatures +;;; Signatures: Dict[publicKeyHash, signature] +tuple _verifySignatures(tuple actions, int hash, cell signatures, cell verifiers, int quorum) impure inline { + int indexedPublicKeyHash = -1; + int validSignatures = 0; + ;; iterate over the signatures dict + do { + (indexedPublicKeyHash, cell signature) = signatures.cl::dict256::getNext(indexedPublicKeyHash); + if (indexedPublicKeyHash != -1) { + (int v, int r, int s) = decodeSignature(signature); + ;; recover the public key from the signature + (_, int x1, int x2, int signatureValid) = ecrecover(hash, v, r, s); + if (signatureValid) { + ;; encode the public key and hash it + int recoveredPublicKeyHash = encodePublicKey(x1, x2).cl::hash(); + ;; check if the recovered public key is in the verifiers dict + ;; and matches the signature in the dict + (_, int verifierExists) = verifiers.cl::dict256::get(recoveredPublicKeyHash); + if ((verifierExists) & (recoveredPublicKeyHash == indexedPublicKeyHash)) { + validSignatures += 1; + } + } else { + ;; emitting this from the proxy will cause the double-call issue + actions~pushAction( + Dvn::event::INVALID_SIGNATURE, + begin_cell().store_uint256(indexedPublicKeyHash).end_cell() + ); + } + } + } until (indexedPublicKeyHash == -1); + + throw_if(Dvn::ERROR::notEnoughSignatures, validSignatures < quorum); + + return actions; +} + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + if (op == Dvn::OP::VERIFY) { + return assertAdmin(); + } elseif ( + (op == UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK) + | (op == Dvn::OP::SET_ADMINS_BY_QUORUM) + ) { + ;; NOP, so no need to check permissions + return (); + } elseif ( + (op == Dvn::OP::SET_VERIFIERS) + | (op == Dvn::OP::SET_QUORUM) + | (op == Worker::OP::SET_ADMINS) + | (op == Worker::OP::SET_PROXY) + | (op == Worker::OP::CLAIM_TON) + | (op == Worker::OP::CLAIM_TON_FROM_PROXY) + | (op == Worker::OP::CALL_VIA_PROXY) + | (op == Dvn::OP::SET_PROXY_ADMINS) + ) { + return assertAdmin(); + } elseif (op == Proxy::OP::HANDLE_CALLBACK) { + return (); + } + throw(BaseInterface::ERROR::invalidOpcode); +} + +;;; ==========================HANDLERS===================================== + +tuple setVerifiers(cell $signedRequest) impure { + (cell $storage, tuple actions) = preamble(); + + cell $setDict = $signedRequest.cl::get(md::SignedRequest::request); + cell $newVerifiers = $setDict.cl::get(md::SetDict::dict); + + throw_if( + Dvn::ERROR::invalidVerifiers, + cl::dict256::size($newVerifiers) < 1 + ); + + int incomingNonce = $setDict.cl::get(md::SetDict::nonce); + + throw_if( + Dvn::ERROR::invalidRequestNonce, + incomingNonce != $storage.cl::get(Dvn::setVerifiersNonce) + ); + + int signedOpcode = $setDict.cl::get(md::SetDict::opcode); + throw_if( + Worker::ERROR::invalidSignedOpcode, + signedOpcode != Dvn::OP::SET_VERIFIERS + ); + + int target = $setDict.cl::get
(md::SetDict::target); + throw_if( + Worker::ERROR::invalidTarget, + target != getContractAddress() + ); + + actions = _verifySignatures( + actions, + $setDict.cl::hash(), + $signedRequest.cl::get(md::SignedRequest::signatures), + $storage.cl::get(Dvn::verifiers), + $storage.cl::get(Dvn::quorum) + ); + + setContractStorage( + $storage + .cl::set(Dvn::verifiers, $newVerifiers) + .cl::set(Dvn::setVerifiersNonce, incomingNonce + 1) + ); + + actions = actions._emitViaProxy( + Dvn::event::SET_VERIFIERS, + $setDict + ); + + return actions; +} + +tuple setQuorum(cell $signedRequest) impure { + (cell $storage, tuple actions) = preamble(); + + cell $setQuorum = $signedRequest.cl::get(md::SignedRequest::request); + + int incomingNonce = $setQuorum.cl::get(md::SetQuorum::nonce); + + throw_if( + Dvn::ERROR::invalidRequestNonce, + incomingNonce != $storage.cl::get(Dvn::setQuorumNonce) + ); + + int signedOpcode = $setQuorum.cl::get(md::SetQuorum::opcode); + throw_if( + Worker::ERROR::invalidSignedOpcode, + signedOpcode != Dvn::OP::SET_QUORUM + ); + + int newQuorum = $setQuorum.cl::get(md::SetQuorum::quorum); + cell $verifiers = $storage.cl::get(Dvn::verifiers); + + throw_if( + Dvn::ERROR::invalidQuorum, + (newQuorum < 1) | (newQuorum > cl::dict256::size($verifiers)) + ); + + int target = $setQuorum.cl::get
(md::SetQuorum::target); + throw_if( + Worker::ERROR::invalidTarget, + target != getContractAddress() + ); + + actions = _verifySignatures( + actions, + $setQuorum.cl::hash(), + $signedRequest.cl::get(md::SignedRequest::signatures), + $storage.cl::get(Dvn::verifiers), + $storage.cl::get(Dvn::quorum) + ); + + setContractStorage( + $storage + .cl::set(Dvn::quorum, newQuorum) + .cl::set(Dvn::setQuorumNonce, incomingNonce + 1) + ); + + actions = actions._emitViaProxy( + Dvn::event::SET_QUORUM, + $setQuorum + ); + + return actions; +} + +tuple setAdminsByQuorum(cell $signedRequest) impure { + (cell $storage, tuple actions) = preamble(); + + cell $setDict = $signedRequest.cl::get(md::SignedRequest::request); + + int incomingNonce = $setDict.cl::get(md::SetDict::nonce); + + throw_if( + Dvn::ERROR::invalidRequestNonce, + incomingNonce != $storage.cl::get(Dvn::setAdminsByQuorumNonce) + ); + + int signedOpcode = $setDict.cl::get(md::SetDict::opcode); + throw_if( + Worker::ERROR::invalidSignedOpcode, + signedOpcode != Dvn::OP::SET_ADMINS_BY_QUORUM + ); + + int target = $setDict.cl::get
(md::SetDict::target); + throw_if( + Worker::ERROR::invalidTarget, + target != getContractAddress() + ); + + actions = _verifySignatures( + actions, + $setDict.cl::hash(), + $signedRequest.cl::get(md::SignedRequest::signatures), + $storage.cl::get(Dvn::verifiers), + $storage.cl::get(Dvn::quorum) + ); + + actions = actions._emitViaProxy( + Dvn::event::SET_ADMINS_BY_QUORUM, + $setDict + ); + + setContractStorage( + $storage + .setCoreStorage( + getCoreStorage().cl::set( + WorkerCoreStorage::admins, + $setDict.cl::get(md::SetDict::dict) + ) + ) + .cl::set(Dvn::setAdminsByQuorumNonce, incomingNonce + 1) + ); + + return actions; +} + +;; Calldata = mdAddress(verification, ulnConnectionAddress) +tuple verify(cell $signedRequest) impure inline { + (cell $storage, tuple actions) = preamble(); + + cell $executeParams = $signedRequest.md::SignedRequest::getRequest(); + + ( + int expiration, + int target, + int signedOpcode, + int forwardingAddress + ) = $executeParams.md::ExecuteParams::deserialize(); + + throw_if( + Dvn::ERROR::expired, + ;; now() counts as expired, per the EVM spec + expiration <= now() + ); + + throw_if( + Worker::ERROR::invalidSignedOpcode, + signedOpcode != Uln::OP::ULN_VERIFY + ); + + throw_if( + Worker::ERROR::invalidTarget, + target != getContractAddress() + ); + + (int quorum, cell verifiers) = $storage.Dvn::getQuorumAndVerifiers(); + + actions = _verifySignatures( + actions, + $executeParams.cl::hash(), + $signedRequest.md::SignedRequest::getSignatures(), + verifiers, + quorum + ); + + ;; for the inside call, the target is the proxy address + $executeParams = $executeParams.cl::set(md::ExecuteParams::target, forwardingAddress); + + actions~pushAction( + getProxyAddress(), + Proxy::OP::CALL_CONTRACT, + $executeParams + ); + + return actions; +} + +tuple verifyCallback(cell $verifyStatus) impure inline { + (cell $storage, tuple actions) = preamble(); + return actions; +} + +tuple setProxyAdmins(cell $signedRequest) impure inline { + (cell $storage, tuple actions) = preamble(); + + cell $executeParams = $signedRequest.cl::get(md::SignedRequest::request); + + throw_if( + Dvn::ERROR::expired, + ;; now() counts as expired, per the EVM spec + $executeParams.cl::get(md::ExecuteParams::expiration) <= now() + ); + + int signedOpcode = $executeParams.cl::get(md::ExecuteParams::opcode); + throw_if( + Worker::ERROR::invalidSignedOpcode, + signedOpcode != Worker::OP::SET_ADMINS + ); + + int target = $executeParams.cl::get
(md::ExecuteParams::target); + throw_if( + Worker::ERROR::invalidTarget, + target != getContractAddress() + ); + + int forwardingAddress = $executeParams.cl::get
(md::ExecuteParams::forwardingAddress); + throw_if( + Worker::ERROR::invalidForwardingAddress, + forwardingAddress != getProxyAddress() + ); + + cell $newAdminList = $executeParams.cl::get(md::ExecuteParams::callData); + throw_if( + Dvn::ERROR::invalidProxyAdmins, + AddressList::length($newAdminList) != 1 + ); + + actions = _verifySignatures( + actions, + $executeParams.cl::hash(), + $signedRequest.cl::get(md::SignedRequest::signatures), + $storage.cl::get(Dvn::verifiers), + $storage.cl::get(Dvn::quorum) + ); + + ;; for the inside call, the target is the proxy address + $executeParams = $executeParams.cl::set(md::ExecuteParams::target, forwardingAddress); + + return _callContract(actions, $executeParams); +} + +tuple setAdmins(cell $addressList) impure inline { + return _setAdmins(emptyActions(), $addressList); +} + +tuple claimTon(cell $claimTon) impure inline { + return _claimTon(emptyActions(), $claimTon); +} + +tuple claimTonFromProxy(cell $claimTon) impure inline { + return _claimTonFromProxy(emptyActions(), $claimTon); +} + +tuple setProxy(cell $setAddress) impure inline { + return _setProxyAddress( + emptyActions(), + $setAddress.cl::get
(md::SetAddress::address) + ); +} + +tuple callViaProxy(cell $executeParams) impure inline { + return _callViaProxy(emptyActions(), $executeParams); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/interface.fc new file mode 100644 index 00000000..79ae08b7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/interface.fc @@ -0,0 +1,23 @@ +;;; ==========================OPCODES===================================== + +const int Dvn::OP::SET_VERIFIERS = "Dvn::OP::SET_VERIFIERS"c; +const int Dvn::OP::SET_QUORUM = "Dvn::OP::SET_QUORUM"c; +const int Dvn::OP::VERIFY = "Dvn::OP::VERIFY"c; +const int Dvn::OP::SET_ADMINS_BY_QUORUM = "Dvn::OP::SET_ADMINS_BY_QUORUM"c; +const int Dvn::OP::SET_PROXY_ADMINS = "Dvn::OP::SET_PROXY_ADMINS"c; + +;;; ==========================ERRORS===================================== +const int Dvn::ERROR::expired = 1983; +const int Dvn::ERROR::quorumNotMet = 1982; +const int Dvn::ERROR::notEnoughSignatures = 1981; +const int Dvn::ERROR::invalidQuorum = 1980; +const int Dvn::ERROR::invalidRequestNonce = 1979; +const int Dvn::ERROR::invalidVerifiers = 1978; +const int Dvn::ERROR::invalidProxyAdmins = 1977; + +;;; ==========================EVENTS===================================== +const int Dvn::event::INVALID_SIGNATURE = "Dvn::event::INVALID_SIGNATURE"u; +const int Dvn::event::SET_VERIFIERS = "Dvn::event::SET_VERIFIERS"u; +const int Dvn::event::SET_QUORUM = "Dvn::event::SET_QUORUM"u; +const int Dvn::event::SET_ADMINS_BY_QUORUM = "Dvn::event::SET_ADMINS_BY_QUORUM"u; +const int Dvn::event::VERIFY = "Dvn::event::VERIFY"u; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/main.fc new file mode 100644 index 00000000..898967b4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/main.fc @@ -0,0 +1,38 @@ +#include "../proxy/interface.fc"; + +#include "../../protocol/msglibs/ultralightnode/callbackOpcodes.fc"; + +#include "../../protocol/core/abstract/protocolMain.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == Dvn::OP::VERIFY) { + return verify($md); + } elseif (op == UltraLightNode::OP::ULN_CONNECTION_VERIFY_CALLBACK) { + return verifyCallback($md); + } elseif (op == Dvn::OP::SET_VERIFIERS) { + return setVerifiers($md); + } elseif (op == Dvn::OP::SET_QUORUM) { + return setQuorum($md); + } elseif (op == Dvn::OP::SET_ADMINS_BY_QUORUM) { + return setAdminsByQuorum($md); + } elseif (op == Worker::OP::SET_ADMINS) { + return setAdmins($md); + } elseif (op == Worker::OP::SET_PROXY) { + return setProxy($md); + } elseif (op == Worker::OP::CLAIM_TON) { + return claimTon($md); + } elseif (op == Worker::OP::CLAIM_TON_FROM_PROXY) { + return claimTonFromProxy($md); + } elseif (op == Dvn::OP::SET_PROXY_ADMINS) { + return setProxyAdmins($md); + } elseif (op == Worker::OP::CALL_VIA_PROXY) { + return callViaProxy($md); + } elseif (op == Proxy::OP::HANDLE_CALLBACK) { + return emptyActions(); + } + throw(BaseInterface::ERROR::invalidOpcode); + return empty_tuple(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/storage.fc new file mode 100644 index 00000000..acbcc64c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/storage.fc @@ -0,0 +1,40 @@ +#include "../core/workerCoreStorage.fc"; + +;; required object name +const int Dvn::NAME = "dvn"u; + +;; field names +const int Dvn::workerCoreStorage = 0; ;; = WORKER_CORE_STORAGE_IDX; +const int Dvn::quorum = 1; +const int Dvn::verifiers = 2; +const int Dvn::setQuorumNonce = 3; +const int Dvn::setVerifiersNonce = 4; +const int Dvn::setAdminsByQuorumNonce = 5; + +;; @owner manager +cell Dvn::New(cell admins, int version, int quorum, cell verifiers) impure inline method_id { + return cl::declare( + Dvn::NAME, + unsafeTuple([ + [cl::t::objRef, WorkerCoreStorage::New(admins, NULLADDRESS, version)], ;; Dvn::workerCoreStorage + [cl::t::uint64, quorum], ;; Dvn::quorum + [cl::t::dict256, verifiers], ;; Dvn::verifiers + [cl::t::uint64, 1], ;; Dvn::setQuorumNonce + [cl::t::uint64, 1], ;; Dvn::setVerifiersNonce + [cl::t::uint64, 1] ;; Dvn::setAdminsByQuorumNonce + ]) + ); +} + +const int Dvn::_quorumOffset = _HEADER_WIDTH; +const int Dvn::_setQuorumNonceOffset = Dvn::_quorumOffset + 64; +const int Dvn::_setVerifiersNonceOffset = Dvn::_setQuorumNonceOffset + 64; +const int Dvn::_setAdminsByQuorumNonceOffset = Dvn::_setVerifiersNonceOffset + 64; + +(int, cell) Dvn::getQuorumAndVerifiers(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint64At(Dvn::_quorumOffset), + selfSlice.preloadRefAt(1) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/main.fc new file mode 100644 index 00000000..b000df58 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/main.fc @@ -0,0 +1,160 @@ +#include "../../core/workerCoreStorage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../workers/core/tests/workerStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/handlerCore.fc"; +#include "../../../funC++/stringlib.fc"; + +#include "../../../../tests/consts.fc"; + +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "dvn"; } + +cell createContractStorage() impure { + cell $storage = Dvn::New( + AddressList::serialize(unsafeTuple([ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS])), + 0, + QUORUM, + cl::dict256::New() + ); + + cell $coreStorage = $storage.cl::get(Dvn::workerCoreStorage).cl::set( + WorkerCoreStorage::proxy, + PROXY_ADDRESS + ); + + $storage = $storage.cl::set(Dvn::workerCoreStorage, $coreStorage); + + setContractStorage($storage); + return $storage; +} + +() _createInitializedStorage() impure { } + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) setAdmins::success::basic() impure { + cell $admins = AddressList::serialize(unsafeTuple( + [getCaller(), ARBITRARY_ADDRESS] + )); + + getContractStorage().setCoreStorage( + getCoreStorage().cl::set( + WorkerCoreStorage::admins, + $admins + ) + ); + return test::handler::shouldPass( + setAdmins, + $admins, + unsafeTuple([ + 0, + _newAction( + Worker::event::ADMINS_SET, + $admins + ) + ]), + getContractStorage().setCoreStorage( + getCoreStorage().cl::set( + WorkerCoreStorage::admins, + $admins + )), + txnContext + ); +} + +(int, slice) setProxyAddress:success::basic() impure { + return test::handler::shouldPass( + setProxy, + md::SetAddress::New(PROXY_ADDRESS), + emptyActions(), + getContractStorage().setCoreStorage( + getCoreStorage().cl::set( + WorkerCoreStorage::proxy, + PROXY_ADDRESS + )), + txnContext + ); +} + +(int, slice) claimTon::success::basic() impure { + return test::handler::shouldPass( + claimTon, + md::ClaimTon::New(MOCK_CLAIM_AMOUNT, ARBITRARY_ADDRESS), + unsafeTuple([ + MOCK_CLAIM_AMOUNT, + _newAction( + ARBITRARY_ADDRESS, + MOCK_CLAIM_AMOUNT, + MOCK_CLAIM_AMOUNT + ), + _newAction( + Worker::event::CLAIMED_TON, + md::ClaimTon::New(MOCK_CLAIM_AMOUNT, ARBITRARY_ADDRESS) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) claimTon::revert::lessThanOneTonRemaining() impure { + int claimAmount = getInitialContractBalance() - ONE_TON + 1; + return test::handler::shouldFail( + claimTon, + md::ClaimTon::New(claimAmount, ARBITRARY_ADDRESS), + Worker::ERROR::insufficientBalance + ); +} + + +(int, slice) claimTonFromProxy::success::basic() impure { + cell $claimTon = md::ClaimTon::New(MOCK_CLAIM_AMOUNT, ARBITRARY_ADDRESS); + + return test::handler::shouldPass( + claimTonFromProxy, + $claimTon, + unsafeTuple([ + 0, + _newAction( + Worker::event::CLAIMED_TON_FROM_PROXY, + $claimTon + ), + _newAction( + PROXY_ADDRESS, + Worker::OP::CLAIM_TON, + $claimTon + ) + ]), + createContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + .tpush([setAdmins::success::basic, "setAdmins::success::basic"]) + .tpush([setProxyAddress:success::basic, "setProxyAddress:success::basic"]) + .tpush([claimTon::success::basic, "claimTon::success::basic"]) + .tpush([claimTon::revert::lessThanOneTonRemaining, "claimTon::revert::lessThanOneTonRemaining"]) + .tpush([claimTonFromProxy::success::basic, "claimTonFromProxy::success::basic"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/permissions.fc new file mode 100644 index 00000000..b06c1362 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/permissions.fc @@ -0,0 +1,225 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../workers/core/tests/workerStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/handlerCore.fc"; + +#include "../../../../tests/consts.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/SetAddress.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "dvn::permissions"; } + +cell createContractStorage() impure { + setContractStorage( + Dvn::New( + AddressList::serialize(unsafeTuple( + [ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS] + )), + 0, + QUORUM, + cl::dict256::New() + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { } + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +(int, slice) checkPermissions::verify::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Dvn::OP::VERIFY, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::verify::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Dvn::OP::VERIFY, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::verify::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Dvn::OP::VERIFY, + cl::nullObject() + ); +} + + +(int, slice) checkPermissions::setVerifiers::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Dvn::OP::SET_VERIFIERS, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setQuorum::revert::notOwner(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Dvn::OP::SET_QUORUM, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdminsByQuorum::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Dvn::OP::SET_ADMINS_BY_QUORUM, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdminsByQuorum::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Dvn::OP::SET_ADMINS_BY_QUORUM, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTon::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTon::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTon::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::CLAIM_TON, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTonFromProxy::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON_FROM_PROXY, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTonFromProxy::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON_FROM_PROXY, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTonFromProxy::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::CLAIM_TON_FROM_PROXY, + cl::nullObject() + ); +} + + +(int, slice) checkPermissions::setAdmins::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_ADMINS, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdmins::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_ADMINS, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdmins::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::SET_ADMINS, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setProxy::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_PROXY, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setProxy::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_PROXY, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setProxy::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::SET_PROXY, + cl::nullObject() + ); +} + +;;; ===============================TESTS========================================= + +tuple baseTest::getTests() impure { + ;; add updater + return unsafeTuple( + empty_tuple() + .tpush([checkPermissions::verify::success::admin1, "checkPermissions::verify::success::admin1"]) + .tpush([checkPermissions::verify::success::admin2, "checkPermissions::verify::success::admin2"]) + .tpush([checkPermissions::verify::revert::notAdmin, "checkPermissions::verify::revert::notAdmin"]) + .tpush([checkPermissions::setVerifiers::revert::notOwner, "checkPermissions::setVerifiers::revert::notOwner"]) + .tpush([checkPermissions::setQuorum::revert::notOwner, "checkPermissions::setQuorum::revert::notOwner"]) + .tpush([checkPermissions::setAdminsByQuorum::success::admin1, "checkPermissions::setAdminsByQuorum::success::admin1"]) + .tpush([checkPermissions::setAdminsByQuorum::success::admin2, "checkPermissions::setAdminsByQuorum::success::admin2"]) + .tpush([checkPermissions::claimTon::success::admin1, "checkPermissions::claimTon::success::admin1"]) + .tpush([checkPermissions::claimTon::success::admin2, "checkPermissions::claimTon::success::admin2"]) + .tpush([checkPermissions::claimTon::revert::notAdmin, "checkPermissions::claimTon::revert::notAdmin"]) + .tpush([checkPermissions::claimTonFromProxy::success::admin1, "checkPermissions::claimTonFromProxy::success::admin1"]) + .tpush([checkPermissions::claimTonFromProxy::success::admin2, "checkPermissions::claimTonFromProxy::success::admin2"]) + .tpush([checkPermissions::claimTonFromProxy::revert::notAdmin, "checkPermissions::claimTonFromProxy::revert::notAdmin"]) + .tpush([checkPermissions::setAdmins::success::admin1, "checkPermissions::setAdmins::success::admin1"]) + .tpush([checkPermissions::setAdmins::success::admin2, "checkPermissions::setAdmins::success::admin2"]) + .tpush([checkPermissions::setAdmins::revert::notAdmin, "checkPermissions::setAdmins::revert::notAdmin"]) + .tpush([checkPermissions::setProxy::success::admin1, "checkPermissions::setProxy::success::admin1"]) + .tpush([checkPermissions::setProxy::success::admin2, "checkPermissions::setProxy::success::admin2"]) + .tpush([checkPermissions::setProxy::revert::notAdmin, "checkPermissions::setProxy::revert::notAdmin"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/serde.fc new file mode 100644 index 00000000..fe38e70b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/dvn/tests/serde.fc @@ -0,0 +1,47 @@ +#include "../storage.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/utils.fc"; + +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Dvn::Serde"; } + +;; Dvn: Has 1 multi-getter (deserializer), +(int, slice) Serde::Dvn::getQuorumAndVerifiers(cell $unused) impure { + cell $dvn = Dvn::New( + MOCK_ADMIN_WORKER_LIST(), + 12, + QUORUM, + cl::dict256::New() + + ); + + tuple $expected = unsafeTuple([ + QUORUM, + cl::dict256::New() + ]); + + ( + int $quorum, + cell $verifiers + ) = Dvn::getQuorumAndVerifiers($dvn); + + return test::multiget::equal( + $dvn, + unsafeTuple([ + Dvn::quorum, + Dvn::verifiers + ]), + unsafeTuple([$quorum, $verifiers]) + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::Dvn::getQuorumAndVerifiers, "Serde::Dvn::getQuorumAndVerifiers"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/handler.fc new file mode 100644 index 00000000..14ec9403 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/handler.fc @@ -0,0 +1,160 @@ +#include "../core/abstract/workerHandler.fc"; +#include "../core/interface.fc"; + +#include "../../classes/msgdata/MdAddress.fc"; +#include "../../classes/msgdata/MdObj.fc"; +#include "../../classes/msgdata/MsglibSendCallback.fc"; +#include "../../classes/msgdata/Nonce.fc"; + +#include "../../protocol/msglibs/interface.fc"; +#include "../../protocol/msglibs/ultralightnode/uln/interface.fc"; +#include "../../protocol/channel/interface.fc"; +#include "../proxy/interface.fc"; + +#include "../msgdata/NativeDrop.fc"; + +#include "interface.fc"; +#include "storage.fc"; + +;;; ================PERMISSION FUNCTIONS===================== + +() _checkPermissions(int op, cell $md) impure inline { + if (op == Proxy::OP::HANDLE_CALLBACK) { + return (); + } + return assertAdmin(); +} + +;;; ==========================HANDLERS===================================== + +tuple _executorNativeDrop(tuple actions, cell $mdNativeDrop) impure inline { + cell $storage = getContractStorage(); + + int totalAmount = 0; + tuple payees = deserializePayees($mdNativeDrop.md::NativeDrop::getPayees()); + + int numPayees = payees.tlen(); + + repeat (numPayees) { + [int payeeAddress, int nativeAmount] = payees~tpopPayee(); + + actions~pushAction( + payeeAddress, + nativeAmount, + 0 + ); + + totalAmount += nativeAmount; + } + + if (numPayees > 0) { + actions = actions._emitViaProxy( + Executor::event::NATIVE_DROP, + $mdNativeDrop + ); + } + + throw_if( + Executor::ERROR::nativeDropTotalCapExceeded, + totalAmount > $storage.Executor::getNativeDropTotalCap() + ); + + return actions; +} + +tuple _executorLzReceivePrepare(tuple actions, cell $executeParams) impure inline { + int opcode = $executeParams.md::ExecuteParams::getOpcode(); + throw_unless( + Worker::ERROR::invalidOpcode, + opcode == Channel::OP::LZ_RECEIVE_PREPARE + ); + + return _callContract(actions, $executeParams); +} + +tuple executorCommitPacket(cell $executeParams) impure inline { + tuple actions = emptyActions(); + int opcode = $executeParams.md::ExecuteParams::getOpcode(); + + throw_unless( + Worker::ERROR::invalidOpcode, + opcode == Uln::OP::ULN_COMMIT_PACKET + ); + + return _callContract(actions, $executeParams); +} + +tuple executorNativeDrop(cell $mdNativeDrop) impure inline { + return _executorNativeDrop(emptyActions(), $mdNativeDrop); +} + +tuple executorLzReceivePrepare(cell $executeParams) impure inline { + return _executorLzReceivePrepare(emptyActions(), $executeParams); +} + +;; md: $mdNativeDrop +;; obj: $executeParams for lzReceivePrepare +tuple executorNativeDropAndLzReceivePrepare(cell $mdObj) impure inline { + ( + cell $executeParams, + cell $nativeDropMd + ) = $mdObj.md::MdObj::deserialize(); + + return emptyActions() + ._executorNativeDrop($nativeDropMd) + ._executorLzReceivePrepare($executeParams); +} + +tuple executorLzReceiveAlert(cell $executeParams) impure inline { + tuple actions = emptyActions(); + + int opcode = $executeParams.md::ExecuteParams::getOpcode(); + throw_unless( + Worker::ERROR::invalidOpcode, + opcode == Channel::OP::EMIT_LZ_RECEIVE_ALERT + ); + + return _callContract(actions, $executeParams); +} + +tuple setAdmins(cell $addressList) impure inline { + return _setAdmins(emptyActions(), $addressList); +} + +tuple claimTon(cell $claimTon) impure inline { + return _claimTon(emptyActions(), $claimTon); +} + +tuple claimTonFromProxy(cell $claimTon) impure inline { + return _claimTonFromProxy(emptyActions(), $claimTon); +} + +tuple setProxy(cell $setAddress) impure inline { + return _setProxyAddress( + emptyActions(), + $setAddress.cl::get
(md::SetAddress::address) + ); +} + +tuple callViaProxy(cell $executeParams) impure inline { + return _callViaProxy(emptyActions(), $executeParams); +} + +tuple setNativeDropTotalCap(cell $coinsAmount) impure inline { + (cell $storage, tuple actions) = preamble(); + $coinsAmount = $coinsAmount.md::CoinsAmount::sanitize(); + + setContractStorage( + $storage.cl::set( + Executor::nativeDropTotalCap, + $coinsAmount.cl::get(md::CoinsAmount::amount) + ) + ); + + actions = actions._emitViaProxy( + Executor::event::NATIVE_DROP_TOTAL_CAP_SET, + $coinsAmount + ); + + return actions; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/interface.fc new file mode 100644 index 00000000..e3982b3b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/interface.fc @@ -0,0 +1,24 @@ +;; --------- Opcodes --------- + + +;; Native drop a batch of native tokens +const int Executor::OP::NATIVE_DROP = "Executor::OP::NATIVE_DROP"c; + +const int Executor::OP::LZ_RECEIVE_PREPARE = "Executor::OP::LZ_RECEIVE_PREPARE"c; + +const int Executor::OP::COMMIT_PACKET = "Executor::OP::COMMIT_PACKET"c; + +;; Native drop a batch of native tokens and execute a packet +const int Executor::OP::NATIVE_DROP_AND_LZ_RECEIVE_PREPARE = "Executor::OP::NATIVE_DROP_AND_LZ_RECEIVE_PREPARE"c; + +const int Executor::OP::LZ_RECEIVE_ALERT = "Executor::OP::LZ_RECEIVE_ALERT"c; + +const int Executor::OP::SET_NATIVE_DROP_TOTAL_CAP = "Executor::OP::SET_NATIVE_DROP_TOTAL_CAP"c; + +;; --------- Events --------- +const int Executor::event::LZ_RECEIVE_ALERT = "Executor::event::LZ_RCV_ALERT"u; +const int Executor::event::NATIVE_DROP = "Executor::event::NATIVE_DROP"u; +const int Executor::event::NATIVE_DROP_TOTAL_CAP_SET = "Executor::event::NATIVDROPCAPSET"u; + +;; --------- Errors --------- +const int Executor::ERROR::nativeDropTotalCapExceeded = 1951; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/main.fc new file mode 100644 index 00000000..a14c21c0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/main.fc @@ -0,0 +1,34 @@ +#include "../../protocol/core/abstract/protocolMain.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == Executor::OP::NATIVE_DROP) { + return executorNativeDrop($md); + } elseif (op == Executor::OP::NATIVE_DROP_AND_LZ_RECEIVE_PREPARE) { + return executorNativeDropAndLzReceivePrepare($md); + } elseif (op == Executor::OP::LZ_RECEIVE_PREPARE) { + return executorLzReceivePrepare($md); + } elseif (op == Executor::OP::LZ_RECEIVE_ALERT) { + return executorLzReceiveAlert($md); + } elseif (op == Worker::OP::SET_ADMINS) { + return setAdmins($md); + } elseif (op == Worker::OP::CLAIM_TON) { + return claimTon($md); + } elseif (op == Worker::OP::CLAIM_TON_FROM_PROXY) { + return claimTonFromProxy($md); + } elseif (op == Worker::OP::CALL_VIA_PROXY) { + return callViaProxy($md); + } elseif (op == Worker::OP::SET_PROXY) { + return setProxy($md); + } elseif (op == Proxy::OP::HANDLE_CALLBACK) { + return emptyActions(); + } elseif (op == Executor::OP::SET_NATIVE_DROP_TOTAL_CAP) { + return setNativeDropTotalCap($md); + } elseif (op == Executor::OP::COMMIT_PACKET) { + return executorCommitPacket($md); + } + throw(BaseInterface::ERROR::invalidOpcode); + return empty_tuple(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/storage.fc new file mode 100644 index 00000000..0bbcae38 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/storage.fc @@ -0,0 +1,26 @@ +#include "../core/workerCoreStorage.fc"; + +;; required object name +const int Executor::NAME = "executor"u; + +;; field names +const int Executor::workerCoreStorage = 0; ;; = WORKER_CORE_STORAGE_IDX +const int Executor::nativeDropTotalCap = 1; + +;; @owner manager +cell Executor::New(cell admins, int version, int nativeDropTotalCap) impure inline method_id { + return cl::declare( + Executor::NAME, + unsafeTuple([ + [cl::t::objRef, WorkerCoreStorage::New(admins, NULLADDRESS, version)], ;; Executor::workerCoreStorage + [cl::t::coins, nativeDropTotalCap] ;; Executor::nativeDropTotalCap + ]) + ); +} + +const int Executor::_nativeDropTotalCapOffset = _HEADER_WIDTH; + +int Executor::getNativeDropTotalCap(cell $self) impure inline { + return $self.cellPreloadCoinsAt(Executor::_nativeDropTotalCapOffset); +} + diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/main.fc new file mode 100644 index 00000000..984c3f0d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/main.fc @@ -0,0 +1,486 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../core/interface.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../workers/core/tests/workerStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; +#include "../../../../tests/consts.fc"; + +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../funC++/constants.fc"; + +#include "../../../classes/msgdata/SetAddress.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/MsglibSendCallback.fc"; +#include "../../../classes/msgdata/LzReceivePrepare.fc"; +#include "../../msgdata/NativeDrop.fc"; +#include "../../../funC++/utils.fc"; +#include "../../../funC++/actions/dispatch.fc"; + +#include "../../../protocol/channel/interface.fc"; +#include "../../../protocol/msglibs/ultralightnode/uln/interface.fc"; + + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "executor"; } + +cell createContractStorage() impure { + setContractStorage( + Executor::New( + AddressList::serialize(unsafeTuple([ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS])), + 0, + NATIVE_DROP_MAX_TOTAL + ) + ); + setProxy( + md::SetAddress::New( + PROXY_ADDRESS + ) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { + initialize( + md::MdObj::build( + cl::dict256::New(), + cl::dict256::New() + ) + ); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) executorLzReceivePrepare::success::basic() impure { + cell $lzReceivePrepareMd = md::LzReceivePrepare::New(1, 0); + + cell $executeParams = md::ExecuteParams::New( + CHANNEL_ADDRESS, + $lzReceivePrepareMd, + 0, + Channel::OP::LZ_RECEIVE_PREPARE, + NULLADDRESS + ); + + return test::handler::shouldPass( + executorLzReceivePrepare, + $executeParams, + unsafeTuple([ + 0, + _newAction( + CHANNEL_ADDRESS, + Channel::OP::LZ_RECEIVE_PREPARE, + $lzReceivePrepareMd + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) executorLzReceivePrepare::fail::invalidOpcode() impure { + cell $lzReceivePrepareMd = md::LzReceivePrepare::New(1, 0); + + cell $executeParams = md::ExecuteParams::New( + CHANNEL_ADDRESS, + $lzReceivePrepareMd, + 0, + OP::RANDOM, + NULLADDRESS + ); + + return test::handler::shouldFail( + executorLzReceivePrepare, + $executeParams, + Worker::ERROR::invalidOpcode + ); +} + +(int, slice) executorCommitPacket::success::basic() impure { + cell calldata = MOCK_PACKET_WITH_MESSAGE(MOCK_MESSAGE()); + + cell $executeParams = md::ExecuteParams::New( + ULN_ADDRESS, + calldata, + 0, + Uln::OP::ULN_COMMIT_PACKET, + NULLADDRESS + ); + + return test::handler::shouldPass( + executorCommitPacket, + $executeParams, + unsafeTuple([ + 0, + _newAction( + ULN_ADDRESS, + Uln::OP::ULN_COMMIT_PACKET, + calldata + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) executorCommitPacket::fail::invalidOpcode() impure { + cell $executeParams = md::ExecuteParams::New( + ULN_ADDRESS, + MOCK_PACKET_WITH_MESSAGE(MOCK_MESSAGE()), + 0, + OP::RANDOM, + NULLADDRESS + ); + + return test::handler::shouldFail( + executorCommitPacket, + $executeParams, + Worker::ERROR::invalidOpcode + ); +} + + +(int, slice) executorNativeDrop::success::noDrops() impure { + return test::handler::shouldPass( + executorNativeDrop, + md::NativeDrop::New(empty_cell(), MOCK_PACKET_ID(), MSGLIB_MANAGER_ADDRESS), + emptyActions(), + getContractStorage(), + txnContext + ); +} + +(int, slice) executorNativeDrop::success::oneDrop() impure { + int dropAmount = 1; + cell $md = md::NativeDrop::New( + serializePayees( + unsafeTuple([ + [ARBITRARY_ADDRESS, dropAmount] + ]) + ), + MOCK_PACKET_ID(), + MSGLIB_MANAGER_ADDRESS + ); + return test::handler::shouldPass( + executorNativeDrop, + $md, + unsafeTuple([ + 0, + _newAction( + ARBITRARY_ADDRESS, + dropAmount, + 0 + ), + _newAction( + PROXY_ADDRESS, + Proxy::OP::EMIT_EVENT, + action::event::New( + Executor::event::NATIVE_DROP, + $md, + cl::nullObject() + ), + _gasToNanoton(PROXY_CALL_DEFAULT_GAS) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) executorNativeDrop::success::maxSingleDrop() impure { + cell $md = md::NativeDrop::New( + serializePayees( + unsafeTuple([ + [ARBITRARY_ADDRESS, NATIVE_DROP_MAX_TOTAL] + ]) + ), + MOCK_PACKET_ID(), + MSGLIB_MANAGER_ADDRESS + ); + + return test::handler::shouldPass( + executorNativeDrop, + $md, + unsafeTuple([ + 0, + _newAction( + ARBITRARY_ADDRESS, + NATIVE_DROP_MAX_TOTAL, + 0 + ), + _newAction( + PROXY_ADDRESS, + Proxy::OP::EMIT_EVENT, + action::event::New( + Executor::event::NATIVE_DROP, + $md, + cl::nullObject() + ), + _gasToNanoton(PROXY_CALL_DEFAULT_GAS) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) executorNativeDrop::success::maxMultiDrop() impure { + int firstDropAmount = NATIVE_DROP_MAX_TOTAL / 2; + int secondDropAmount = NATIVE_DROP_MAX_TOTAL - firstDropAmount; + cell $md = md::NativeDrop::New( + serializePayees( + unsafeTuple([ + [ARBITRARY_ADDRESS, firstDropAmount], + [ARBITRARY_ADDRESS + 1, secondDropAmount] + ]) + ), + MOCK_PACKET_ID(), + MSGLIB_MANAGER_ADDRESS + ); + + return test::handler::shouldPass( + executorNativeDrop, + $md, + unsafeTuple([ + 0, + _newAction(ARBITRARY_ADDRESS, firstDropAmount, 0), + _newAction(ARBITRARY_ADDRESS + 1, secondDropAmount, 0), + _newAction( + PROXY_ADDRESS, + Proxy::OP::EMIT_EVENT, + action::event::New( + Executor::event::NATIVE_DROP, + $md, + cl::nullObject() + ), + _gasToNanoton(PROXY_CALL_DEFAULT_GAS) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) executorNativeDrop::success::multipleDrops() impure { + tuple payees = unsafeTuple([ + [ARBITRARY_ADDRESS, NATIVE_DROP_AMOUNT], + [ARBITRARY_ADDRESS + 1, NATIVE_DROP_AMOUNT + 1], + [ARBITRARY_ADDRESS + 2, NATIVE_DROP_AMOUNT + 2], + [ARBITRARY_ADDRESS + 3, NATIVE_DROP_AMOUNT + 3], + [ARBITRARY_ADDRESS + 4, NATIVE_DROP_AMOUNT + 4], + [ARBITRARY_ADDRESS + 5, NATIVE_DROP_AMOUNT + 5], + [ARBITRARY_ADDRESS + 6, NATIVE_DROP_AMOUNT + 6] + ]); + cell $md = md::NativeDrop::New( + serializePayees(payees), + MOCK_PACKET_ID(), + MSGLIB_MANAGER_ADDRESS + ); + + tuple expectedActions = emptyActions() + .tpush(_newAction(ARBITRARY_ADDRESS + 5, NATIVE_DROP_AMOUNT + 5, 0)) + .tpush(_newAction(ARBITRARY_ADDRESS + 6, NATIVE_DROP_AMOUNT + 6, 0)) + .tpush(_newAction(ARBITRARY_ADDRESS + 2, NATIVE_DROP_AMOUNT + 2, 0)) + .tpush(_newAction(ARBITRARY_ADDRESS + 3, NATIVE_DROP_AMOUNT + 3, 0)) + .tpush(_newAction(ARBITRARY_ADDRESS + 4, NATIVE_DROP_AMOUNT + 4, 0)) + .tpush(_newAction(ARBITRARY_ADDRESS, NATIVE_DROP_AMOUNT, 0)) + .tpush(_newAction(ARBITRARY_ADDRESS + 1, NATIVE_DROP_AMOUNT + 1, 0)) + .tpush(_newAction( + PROXY_ADDRESS, + Proxy::OP::EMIT_EVENT, + action::event::New( + Executor::event::NATIVE_DROP, + $md, + cl::nullObject() + ), + _gasToNanoton(PROXY_CALL_DEFAULT_GAS) + )); + + return test::handler::shouldPass( + executorNativeDrop, + $md, + expectedActions, + getContractStorage(), + txnContext + ); +} + +(int, slice) executorNativeDrop::fail::totalCapExceededSingleDrop() impure { + int dropAmount = getContractStorage().cl::get(Executor::nativeDropTotalCap) + 1; + return test::handler::shouldFail( + executorNativeDrop, + md::NativeDrop::New( + serializePayees( + unsafeTuple([ + [ARBITRARY_ADDRESS, dropAmount] + ]) + ), + MOCK_PACKET_ID(), + MSGLIB_MANAGER_ADDRESS + ), + Executor::ERROR::nativeDropTotalCapExceeded + ); +} + +(int, slice) executorNativeDrop::fail::totalCapExceededMultipleDrops() impure { + int dropAmount = getContractStorage().cl::get(Executor::nativeDropTotalCap) + 1; + + tuple payees = unsafeTuple([ + [ARBITRARY_ADDRESS, NATIVE_DROP_MAX_TOTAL], + [ARBITRARY_ADDRESS + 1, dropAmount] + ]); + + return test::handler::shouldFail( + executorNativeDrop, + md::NativeDrop::New( + serializePayees(payees), + MOCK_PACKET_ID(), + MSGLIB_MANAGER_ADDRESS + ), + Executor::ERROR::nativeDropTotalCapExceeded + ); +} + +(int, slice) executorLzReceiveAlert::success::basic() impure { + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + cl::nullObject(), + 0, + Channel::OP::EMIT_LZ_RECEIVE_ALERT, + NULLADDRESS + ); + return test::handler::shouldPass( + executorLzReceiveAlert, + $executeParams, + unsafeTuple([ + 0, + _newAction( + ARBITRARY_ADDRESS, + Channel::OP::EMIT_LZ_RECEIVE_ALERT, + cl::nullObject() + ) + ]), + getContractStorage(), + txnContext + ); +} + + +(int, slice) executorNativeDropAndLzReceivePrepare::success::basic() impure { + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + cl::nullObject(), + 0, + Channel::OP::LZ_RECEIVE_PREPARE, + NULLADDRESS + ); + + cell $nativeDrop = md::NativeDrop::New( + empty_cell(), + MOCK_PACKET_ID(), + MSGLIB_MANAGER_ADDRESS + ); + cell $mdObj = md::MdObj::build( + $executeParams, + $nativeDrop + ); + + return test::handler::shouldPass( + executorNativeDropAndLzReceivePrepare, + $mdObj, + unsafeTuple([ + 0, + _newAction( + ARBITRARY_ADDRESS, + Channel::OP::LZ_RECEIVE_PREPARE, + cl::nullObject() + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) executorNativeDropAndLzReceivePrepare::fail::invalidOpcode() impure { + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + cl::nullObject(), + 0, + OP::RANDOM, + NULLADDRESS + ); + + cell $nativeDrop = md::NativeDrop::New( + empty_cell(), + MOCK_PACKET_ID(), + MSGLIB_MANAGER_ADDRESS + ); + + cell $mdObj = md::MdObj::build( + $executeParams, + $nativeDrop + ); + + return test::handler::shouldFail( + executorNativeDropAndLzReceivePrepare, + $mdObj, + Worker::ERROR::invalidOpcode + ); +} + +(int, slice) setNativeDropTotalCap::success::basic() impure { + cell $coinsAmount = md::CoinsAmount::New(NATIVE_DROP_MAX_TOTAL / 2); + return test::handler::shouldPass( + setNativeDropTotalCap, + $coinsAmount, + unsafeTuple([ + 0, + _newAction( + PROXY_ADDRESS, + Proxy::OP::EMIT_EVENT, + action::event::New( + Executor::event::NATIVE_DROP_TOTAL_CAP_SET, + $coinsAmount, + cl::nullObject() + ), + _gasToNanoton(PROXY_CALL_DEFAULT_GAS) + ) + ]), + getContractStorage().cl::set( + Executor::nativeDropTotalCap, + $coinsAmount.cl::get(md::CoinsAmount::amount) + ), + txnContext + ); +} + + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + .tpush([executorLzReceivePrepare::success::basic, "executorLzReceivePrepare::success::basic"]) + .tpush([executorLzReceivePrepare::fail::invalidOpcode, "executorLzReceivePrepare::fail::invalidOpcode"]) + ;; .tpush([executorCommitPacket::success::basic, "executorCommitPacket::success::basic"]) + .tpush([executorNativeDrop::success::noDrops, "executorNativeDrop::success::noDrops"]) + .tpush([executorNativeDrop::success::oneDrop, "executorNativeDrop::success::oneDrop"]) + .tpush([executorNativeDrop::success::multipleDrops, "executorNativeDrop::success::multipleDrops"]) + .tpush([executorNativeDrop::success::maxSingleDrop, "executorNativeDrop::success::maxSingleDrop"]) + .tpush([executorNativeDrop::success::maxMultiDrop, "executorNativeDrop::success::maxMultiDrop"]) + .tpush([executorNativeDrop::fail::totalCapExceededSingleDrop, "executorNativeDrop::fail::totalCapExceededSingleDrop"]) + .tpush([executorNativeDrop::fail::totalCapExceededMultipleDrops, "executorNativeDrop::fail::totalCapExceededMultipleDrops"]) + .tpush([executorLzReceiveAlert::success::basic, "executorLzReceiveAlert::success::basic"]) + .tpush([executorNativeDropAndLzReceivePrepare::success::basic, "executorNativeDropAndLzReceivePrepare::success::basic"]) + .tpush([setNativeDropTotalCap::success::basic, "setNativeDropTotalCap::success::basic"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/permissions.fc new file mode 100644 index 00000000..0850ed94 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/permissions.fc @@ -0,0 +1,237 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../workers/core/tests/workerStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/handlerCore.fc"; + +#include "../../../../tests/consts.fc"; +#include "../../../classes/msgdata/MdAddress.fc"; +#include "../../../classes/msgdata/SetAddress.fc"; +#include "../../../funC++/abstract/handlerAbstract.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "executor::permissions"; } + +cell createContractStorage() impure { + setContractStorage( + Executor::New( + AddressList::serialize(unsafeTuple( + [ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS] + )), + 0, + NATIVE_DROP_MAX_TOTAL + ) + ); + + return getContractStorage(); +} + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +(int, slice) checkPermissions::nativeDrop::success::admin1() impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Executor::OP::NATIVE_DROP, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::nativeDrop::success::admin2() impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Executor::OP::NATIVE_DROP, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::nativeDrop::revert::notAdmin() impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Executor::OP::NATIVE_DROP, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::nativeDropAndLzReceivePrepare::success::admin1() impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Executor::OP::NATIVE_DROP_AND_LZ_RECEIVE_PREPARE, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::nativeDropAndLzReceivePrepare::success::admin2() impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Executor::OP::NATIVE_DROP_AND_LZ_RECEIVE_PREPARE, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::nativeDropAndLzReceivePrepare::revert::notAdmin() impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Executor::OP::NATIVE_DROP_AND_LZ_RECEIVE_PREPARE, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::executorLzReceiveAlert::success::admin1() impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Executor::OP::LZ_RECEIVE_ALERT, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::executorLzReceiveAlert::success::admin2() impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Executor::OP::LZ_RECEIVE_ALERT, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::executorLzReceiveAlert::revert::notAdmin() impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Executor::OP::LZ_RECEIVE_ALERT, + cl::nullObject() + ); +} + + +(int, slice) checkPermissions::callViaProxy::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CALL_VIA_PROXY, + MOCK_DEFAULT_PRICE_FEED_STORAGE() + ); +} + +(int, slice) checkPermissions::callViaProxy::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CALL_VIA_PROXY, + MOCK_DEFAULT_PRICE_FEED_STORAGE() + ); +} + +(int, slice) checkPermissions::callViaProxy::notUpdater(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::CALL_VIA_PROXY, + MOCK_DEFAULT_PRICE_FEED_STORAGE() + ); +} + +(int, slice) checkPermissions::claimTon::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTon::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTon::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::CLAIM_TON, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdmins::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_ADMINS, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdmins::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_ADMINS, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdmins::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::SET_ADMINS, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setProxy::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_PROXY, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setProxy::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_PROXY, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setProxy::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::SET_PROXY, + cl::nullObject() + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([checkPermissions::nativeDrop::success::admin1, "checkPermissions::nativeDrop::success::admin1"]) + .tpush([checkPermissions::nativeDrop::success::admin2, "checkPermissions::nativeDrop::success::admin2"]) + .tpush([checkPermissions::nativeDrop::revert::notAdmin, "checkPermissions::nativeDrop::revert::notAdmin"]) + .tpush([checkPermissions::nativeDropAndLzReceivePrepare::success::admin1, "checkPermissions::nativeDropAndLzReceivePrepare::success::admin1"]) + .tpush([checkPermissions::nativeDropAndLzReceivePrepare::success::admin2, "checkPermissions::nativeDropAndLzReceivePrepare::success::admin2"]) + .tpush([checkPermissions::nativeDropAndLzReceivePrepare::revert::notAdmin, "checkPermissions::nativeDropAndLzReceivePrepare::revert::notAdmin"]) + .tpush([checkPermissions::executorLzReceiveAlert::success::admin1, "checkPermissions::executorLzReceiveAlert::success::admin1"]) + .tpush([checkPermissions::executorLzReceiveAlert::success::admin2, "checkPermissions::executorLzReceiveAlert::success::admin2"]) + .tpush([checkPermissions::executorLzReceiveAlert::revert::notAdmin, "checkPermissions::executorLzReceiveAlert::revert::notAdmin"]) + .tpush([checkPermissions::callViaProxy::success::admin1, "checkPermissions::callViaProxy::success::admin1"]) + .tpush([checkPermissions::callViaProxy::success::admin2, "checkPermissions::callViaProxy::success::admin2"]) + .tpush([checkPermissions::callViaProxy::notUpdater, "checkPermissions::callViaProxy::notUpdater"]) + .tpush([checkPermissions::claimTon::success::admin1, "checkPermissions::claimTon::success::admin1"]) + .tpush([checkPermissions::claimTon::success::admin2, "checkPermissions::claimTon::success::admin2"]) + .tpush([checkPermissions::claimTon::revert::notAdmin, "checkPermissions::claimTon::revert::notAdmin"]) + .tpush([checkPermissions::setAdmins::success::admin1, "checkPermissions::setAdmins::success::admin1"]) + .tpush([checkPermissions::setAdmins::success::admin2, "checkPermissions::setAdmins::success::admin2"]) + .tpush([checkPermissions::setAdmins::revert::notAdmin, "checkPermissions::setAdmins::revert::notAdmin"]) + .tpush([checkPermissions::setProxy::success::admin1, "checkPermissions::setProxy::success::admin1"]) + .tpush([checkPermissions::setProxy::success::admin2, "checkPermissions::setProxy::success::admin2"]) + .tpush([checkPermissions::setProxy::revert::notAdmin, "checkPermissions::setProxy::revert::notAdmin"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/serde.fc new file mode 100644 index 00000000..3dcc0fef --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/executor/tests/serde.fc @@ -0,0 +1,31 @@ +#include "../storage.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/utils.fc"; + +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Executor::Serde"; } + +;; Executor: Has 1 getter +(int, slice) Serde::Executor::getNativeDropTotalCap(cell $self) impure { + cell $executor = Executor::New( + MOCK_ADMIN_WORKER_LIST(), + 0, + 1 + ); + return test::getData::equal( + $executor, + Executor::getNativeDropTotalCap, + Executor::nativeDropTotalCap + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::Executor::getNativeDropTotalCap, "Serde::Executor::getNativeDropTotalCap"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/ClaimTon.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/ClaimTon.fc new file mode 100644 index 00000000..dc64ab70 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/ClaimTon.fc @@ -0,0 +1,18 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::ClaimTon::NAME = "claimTon"u; + +;; field names +const int md::ClaimTon::amount = 0; +const int md::ClaimTon::target = 1; + +cell md::ClaimTon::New(int amount, int target) impure inline method_id { + return cl::declare( + md::ClaimTon::NAME, + unsafeTuple([ + [cl::t::coins, amount], ;; md::ClaimTon::amount + [cl::t::address, target] ;; md::ClaimTon::target + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/ExecuteParams.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/ExecuteParams.fc new file mode 100644 index 00000000..357111b3 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/ExecuteParams.fc @@ -0,0 +1,92 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::ExecuteParams::NAME = "execParams"u; + +;; struct ExecuteParam { +;; uint32 vid; // no vid? +;; address target; +;; bytes callData; +;; uint256 expiration; +;; } + +;; field names +const int md::ExecuteParams::target = 0; +const int md::ExecuteParams::callData = 1; +const int md::ExecuteParams::expiration = 2; +const int md::ExecuteParams::opcode = 3; +const int md::ExecuteParams::forwardingAddress = 4; + +cell md::ExecuteParams::New( + int target, + cell callData, + int expiration, + int opcode, + int forwardingAddress +) impure inline method_id { + return cl::declare( + md::ExecuteParams::NAME, + unsafeTuple([ + [cl::t::address, target], ;; md::ExecuteParams::target + [cl::t::objRef, callData], ;; md::ExecuteParams::callData + [cl::t::uint64, expiration], ;; md::ExecuteParams::expiration + [cl::t::uint32, opcode], ;; md::ExecuteParams::opcode + [cl::t::address, forwardingAddress] ;; md::ExecuteParams::forwardingAddress + ]) + ); +} + +;; ========================== Object Builders===========================w========== + +const int md::ExecuteParams::_headerInfoBits = _BASIC_HEADER_WIDTH + (_FIELD_INFO_WIDTH * 5); +const int md::ExecuteParams::_headerFillerBits = _HEADER_WIDTH - md::ExecuteParams::_headerInfoBits; + +cell md::ExecuteParams::build( + int target, + cell callData, + int expiration, + int opcode, + int forwardingAddress +) impure inline { + return begin_cell() + .store_uint(593196021158614130743421175296147950525415477349115, md::ExecuteParams::_headerInfoBits) + .store_ones(md::ExecuteParams::_headerFillerBits) + .store_uint256(target) + .store_ref(callData) + .store_uint64(expiration) + .store_uint32(opcode) + .store_uint256(forwardingAddress) + .end_cell(); +} + +;; ========================== Object Getters===================================== + +const int md::ExecuteParams::_targetOffset = _HEADER_WIDTH; +const int md::ExecuteParams::_expirationOffset = md::ExecuteParams::_targetOffset + 256; +const int md::ExecuteParams::_opcodeOffset = md::ExecuteParams::_expirationOffset + 64; +const int md::ExecuteParams::_forwardingAddressOffset = md::ExecuteParams::_opcodeOffset + 32; + +int md::ExecuteParams::getOpcode(cell $self) impure inline { + return $self.cellPreloadUint32At(md::ExecuteParams::_opcodeOffset); +} + +;; (expiration, target, opcode, forwardingAddress) +(int, int, int, int) md::ExecuteParams::deserialize(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadUint64At(md::ExecuteParams::_expirationOffset), + selfSlice.preloadAddressAt(md::ExecuteParams::_targetOffset), + selfSlice.preloadUint32At(md::ExecuteParams::_opcodeOffset), + selfSlice.preloadAddressAt(md::ExecuteParams::_forwardingAddressOffset) + ); +} + +;; (target, opcode, callData) +(int, int, cell) md::ExecuteParams::getActionArgs(cell $self) impure inline { + slice selfSlice = $self.begin_parse(); + return ( + selfSlice.preloadAddressAt(md::ExecuteParams::_targetOffset), + selfSlice.preloadUint32At(md::ExecuteParams::_opcodeOffset), + selfSlice.preload_ref_at(0) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/NativeDrop.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/NativeDrop.fc new file mode 100644 index 00000000..b7a39620 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/NativeDrop.fc @@ -0,0 +1,24 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::NativeDrop::NAME = "NativeDrop"u; + +;; field names +const int md::NativeDrop::payees = 0; +const int md::NativeDrop::packetId = 1; +const int md::NativeDrop::msglib = 2; + +cell md::NativeDrop::New(cell payees, cell $packetId, int msglib) impure inline method_id { + return cl::declare( + md::NativeDrop::NAME, + unsafeTuple([ + [cl::t::cellRef, payees], ;; md::NativeDrop::payees + [cl::t::objRef, $packetId], ;; md::NativeDrop::packetId + [cl::t::address, msglib] ;; md::NativeDrop::msgLib + ]) + ); +} + +cell md::NativeDrop::getPayees(cell $self) impure inline { + return $self.cellPreloadRefAt(0); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SetDict.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SetDict.fc new file mode 100644 index 00000000..3d0c73b5 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SetDict.fc @@ -0,0 +1,22 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::SetDict::NAME = "setDct"u; + +;; field names +const int md::SetDict::nonce = 0; +const int md::SetDict::opcode = 1; +const int md::SetDict::dict = 2; +const int md::SetDict::target = 3; + +cell md::SetDict::New(int nonce, int opcode, cell _dict, int target) impure inline method_id { + return cl::declare( + md::SetDict::NAME, + unsafeTuple([ + [cl::t::uint64, nonce], ;; md::SetDict::nonce + [cl::t::uint64, opcode], ;; md::SetDict::opcode + [cl::t::dict256, _dict], ;; md::SetDict::dict + [cl::t::address, target] ;; md::SetDict::target + ]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SetQuorum.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SetQuorum.fc new file mode 100644 index 00000000..fa5446e5 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SetQuorum.fc @@ -0,0 +1,22 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::SetQuorum::NAME = "setQuorum"u; + +;; field names +const int md::SetQuorum::nonce = 0; +const int md::SetQuorum::opcode = 1; +const int md::SetQuorum::quorum = 2; +const int md::SetQuorum::target = 3; + +cell md::SetQuorum::New(int nonce, int opcode, int quorum, int target) impure inline method_id { + return cl::declare( + md::SetQuorum::NAME, + unsafeTuple([ + [cl::t::uint64, nonce], ;; md::SetQuorum::nonce + [cl::t::uint64, opcode], ;; md::SetQuorum::opcode + [cl::t::uint64, quorum], ;; md::SetQuorum::quorum + [cl::t::address, target] ;; md::SetQuorum::target + ]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SignedRequest.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SignedRequest.fc new file mode 100644 index 00000000..fc4f3917 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/SignedRequest.fc @@ -0,0 +1,26 @@ +#include "../../funC++/classlib.fc"; + +;; required storage name +const int md::SignedRequest::NAME = "sgndReq"u; + +;; field names +const int md::SignedRequest::request = 0; +const int md::SignedRequest::signatures = 1; + +cell md::SignedRequest::New(cell $request, cell signatures) impure inline method_id { + return cl::declare( + md::SignedRequest::NAME, + unsafeTuple([ + [cl::t::objRef, $request], ;; md::SignedRequest::request + [cl::t::dict256, signatures] ;; md::SignedRequest::signaturesk + ]) + ); +} + +cell md::SignedRequest::getRequest(cell $self) impure inline { + return $self.begin_parse().preload_ref_at(0); +} + +cell md::SignedRequest::getSignatures(cell $self) impure inline { + return $self.begin_parse().preload_ref_at(1); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/tests/serde.fc new file mode 100644 index 00000000..84f59dab --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/msgdata/tests/serde.fc @@ -0,0 +1,171 @@ +#include "../ExecuteParams.fc"; +#include "../NativeDrop.fc"; +#include "../SignedRequest.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/stdlib.fc"; +#include "../../../funC++/utils.fc"; + +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Workers MsgData Serde"; } + +;; ExecuteParams: Has 1 build +;; Has 1 getter +;; Has 2 multi-getter (deserializer), +(int, slice) Serde::ExecuteParams::build(cell $unused) impure { + cell callData = begin_cell().store_uint(0, 32).store_uint256(1).store_uint(0, 32).store_uint256(1).end_cell(); + + return test::build::equal( + md::ExecuteParams::New(CHANNEL_ADDRESS, callData, 1, 2, NULLADDRESS), + md::ExecuteParams::build(CHANNEL_ADDRESS, callData, 1, 2, NULLADDRESS) + ); +} + +(int, slice) Serde::ExecuteParams::getOpcode(cell $self) impure { + cell $executeParams = md::ExecuteParams::New( + CHANNEL_ADDRESS, + begin_cell().end_cell(), + 1, + 2, + NULLADDRESS + ); + + return test::getData::equal( + $executeParams, + md::ExecuteParams::getOpcode, + md::ExecuteParams::opcode + ); +} + +(int, slice) Serde::ExecuteParams::deserialize(cell $self) impure { + int expectedExpiration = 3; + int expectedOpcode = 1; + int expectedTarget = CHANNEL_ADDRESS; + int expectedForwardingAddress = ENDPOINT_ADDRESS; + cell $executeParams = md::ExecuteParams::New( + expectedTarget, + begin_cell().end_cell(), + expectedExpiration, + expectedOpcode, + expectedForwardingAddress + ); + + ( + int expiration, + int target, + int opcode, + int forwardingAddress + ) = $executeParams.md::ExecuteParams::deserialize(); + + return test::multiget::equal( + $executeParams, + unsafeTuple([ + md::ExecuteParams::expiration, + md::ExecuteParams::target, + md::ExecuteParams::opcode, + md::ExecuteParams::forwardingAddress + ]), + unsafeTuple([ + expiration, + target, + opcode, + forwardingAddress + ]) + ); +} + +(int, slice) Serde::ExecuteParams::getActionArgs(cell $self) impure { + int expectedExpiration = 3; + int expectedOpcode = 1; + cell expectedCallData = begin_cell() + .store_uint(0, 32) + .store_uint256(1) + .store_uint(0, 32) + .store_uint256(1) + .end_cell(); + + cell $executeParams = md::ExecuteParams::New( + CHANNEL_ADDRESS, + expectedCallData, + expectedExpiration, + expectedOpcode, + NULLADDRESS + ); + + (int target, int opcode, cell callData) = $executeParams.md::ExecuteParams::getActionArgs(); + + return test::multiget::equal( + $executeParams, + unsafeTuple([ + md::ExecuteParams::target, + md::ExecuteParams::opcode, + md::ExecuteParams::callData + ]), + unsafeTuple([ + target, + opcode, + callData + ]) + ); +} + +;; NativeDrop: Has 1 getter +(int, slice) Serde::NativeDrop::getPayees(cell $self) impure { + cell $nativeDrop = md::NativeDrop::New( + serializePayees(MOCK_PAYEES(5)), + MOCK_PACKET_ID(), + MSGLIB_MANAGER_ADDRESS + ); + + return test::getRef::equal( + $nativeDrop, + md::NativeDrop::getPayees, + md::NativeDrop::payees + ); +} + +;; SignedRequest: Has 2 getters +(int, slice) Serde::SignedReuest::getRequest(cell $self) impure { + cell $request = md::SignedRequest::New( + begin_cell().store_uint(1, 256).end_cell(), + begin_cell().store_uint(2, 256).end_cell() + ); + + return test::getRef::equal( + $request, + md::SignedRequest::getRequest, + md::SignedRequest::request + ); +} + +(int, slice) Serde::SignedReuest::getSignatures(cell $self) impure { + cell $request = md::SignedRequest::New( + begin_cell().store_uint(1, 256).end_cell(), + begin_cell().store_uint(2, 256).end_cell() + ); + + return test::getRef::equal( + $request, + md::SignedRequest::getSignatures, + md::SignedRequest::signatures + ); +} + +tuple baseTest::getTests() impure { + return empty_tuple() + ;; -- ExecuteParams + .tpush([Serde::ExecuteParams::build, "Serde::ExecuteParams::build"]) + .tpush([Serde::ExecuteParams::getOpcode, "Serde::ExecuteParams::getOpcode"]) + .tpush([Serde::ExecuteParams::deserialize, "Serde::ExecuteParams::deserialize"]) + .tpush([Serde::ExecuteParams::getActionArgs, "Serde::ExecuteParams::getActionArgs"]) + ;; -- NativeDrop + .tpush([Serde::NativeDrop::getPayees, "Serde::NativeDrop::getPayees"]) + ;; -- SignedRequest + .tpush([Serde::SignedReuest::getRequest, "Serde::SignedReuest::getRequest"]) + .tpush([Serde::SignedReuest::getSignatures, "Serde::SignedReuest::getSignatures"]) + ; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/handler.fc new file mode 100644 index 00000000..0c3672a1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/handler.fc @@ -0,0 +1,228 @@ +#include "../core/abstract/workerHandler.fc"; + +#include "../core/interface.fc"; + +#include "../../funC++/handlerCore.fc"; + +#include "../../classes/msgdata/MdAddress.fc"; + +#include "../msgdata/ExecuteParams.fc"; + +#include "../proxy/interface.fc"; +#include "../../protocol/channel/interface.fc"; +#include "../../protocol/msglibs/ultralightnode/uln/interface.fc"; + +#include "../../protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/storage.fc"; + +#include "interface.fc"; +#include "storage.fc"; + +;;; ==========================PERMISSION FUNCTIONS================================= + +;; any of the admins can update the priceFeed cache -> priceFeedFeeLib +;; but only the owner can call "callContract" from the priceFeedCache +() _checkPermissions(int op, cell $md) impure inline { + if (op == Proxy::OP::HANDLE_CALLBACK) { + return (); + } + return assertAdmin(); +} + +;;; ==========================HANDLERS===================================== + +tuple claimTon(cell $coinsAmount) impure inline { + return _claimTon(emptyActions(), $coinsAmount); +} + +tuple updatePrice(cell $mdAddress) impure inline { + (cell $storage, tuple actions) = preamble(); + + (cell $ulnPriceFeed, int targetAddress) = $mdAddress.md::MdAddress::deserialize(); + ( + int priceRatio, + int gasPriceInUnit, + int gasPerByte + ) = PriceFeedFeelib::utils::getFirstThreeFields($ulnPriceFeed); + + cell $newPfStorage = $storage + .PriceFeedCache::getPriceFeedFeeLibStorage() + .PriceFeedFeelib::setFirstThreeFields(priceRatio, gasPriceInUnit, gasPerByte); + + setContractStorage( + $storage.PriceFeedCache::setPriceFeedFeeLibStorage($newPfStorage) + ); + + ;; call into the proxy to update the priceFeedFeeLib + return _callViaProxy( + actions, + md::ExecuteParams::build( + targetAddress, ;; usually the uln + $newPfStorage, ;; new priceFeedFeeLib storage + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ) + ); +} + +tuple updateNativePrice(cell $mdAddress) impure inline { + (cell $storage, tuple actions) = preamble(); + + (cell $ulnPriceFeed, int targetAddress) = $mdAddress.md::MdAddress::deserialize(); + int nativePriceUsd = $ulnPriceFeed.PriceFeedFeelib::getNativePriceUsd(); + + cell $newPfStorage = $storage + .PriceFeedCache::getPriceFeedFeeLibStorage() + .PriceFeedFeelib::setNativePriceUsd(nativePriceUsd); + + setContractStorage( + $storage.PriceFeedCache::setPriceFeedFeeLibStorage($newPfStorage) + ); + + ;; call into the proxy to update the priceFeedFeeLib + return _callViaProxy( + actions, + md::ExecuteParams::build( + targetAddress, ;; usually the uln + $newPfStorage, ;; new priceFeedFeeLib storage + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ) + ); +} + +;; ------- custom handlers for op/arb price feed caches ------- + +;; update base price and op price +tuple updateOpPrices(cell $mdAddress) impure inline { + (cell $storage, tuple actions) = preamble(); + + cell $oldPfStorage = $storage.PriceFeedCache::getPriceFeedFeeLibStorage(); + cell $oldOpExtension = $oldPfStorage.PriceFeedFeelib::getOptimismExtension(); + + (cell $ulnPriceFeed, int targetAddress) = $mdAddress.md::MdAddress::deserialize(); + ( + int priceRatio, + int gasPriceInUnit, + int gasPerByte + ) = PriceFeedFeelib::utils::getFirstThreeFields($ulnPriceFeed); + + cell $incomingOpExtension = $ulnPriceFeed.PriceFeedFeelib::getOptimismExtension(); + ( + int opPriceRatio, + int opGasPriceInUnit, + int opGasPerByte + ) = PriceFeedFeelib::utils::getFirstThreeFields($incomingOpExtension); + + cell $newOpExtension = $oldOpExtension.PriceFeedFeelib::setFirstThreeFields( + opPriceRatio, + opGasPriceInUnit, + opGasPerByte + ); + + cell $newPfStorage = $oldPfStorage + .PriceFeedFeelib::setPricesAndOptimismExtension( + priceRatio, + gasPriceInUnit, + gasPerByte, + $newOpExtension + ); + + setContractStorage( + $storage.PriceFeedCache::setPriceFeedFeeLibStorage($newPfStorage) + ); + + ;; call into the proxy to update the priceFeedFeeLib + return _callViaProxy( + actions, + md::ExecuteParams::build( + targetAddress, ;; usually the uln + $newPfStorage, ;; new priceFeedFeeLib storage + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ) + ); +} + +;; update base native price and op native price +tuple updateOpNativePrices(cell $mdAddress) impure inline { + (cell $storage, tuple actions) = preamble(); + + cell $oldPfStorage = $storage.PriceFeedCache::getPriceFeedFeeLibStorage(); + cell $oldOpExtension = $oldPfStorage.PriceFeedFeelib::getOptimismExtension(); + + (cell $ulnPriceFeed, int targetAddress) = $mdAddress.md::MdAddress::deserialize(); + + int nativePriceUsd = $ulnPriceFeed.PriceFeedFeelib::getNativePriceUsd(); + cell $incomingOpExtension = $ulnPriceFeed.PriceFeedFeelib::getOptimismExtension(); + + int opNativePriceUsd = $incomingOpExtension.PriceFeedFeelib::getNativePriceUsd(); + cell $newOpExtension = $oldOpExtension.PriceFeedFeelib::setNativePriceUsd(opNativePriceUsd); + + cell $newPfStorage = $oldPfStorage + .PriceFeedFeelib::setNativePriceUsdAndOptimismExtension( + nativePriceUsd, + $newOpExtension + ); + + setContractStorage( + $storage.PriceFeedCache::setPriceFeedFeeLibStorage($newPfStorage) + ); + + ;; call into the proxy to update the priceFeedFeeLib + return _callViaProxy( + actions, + md::ExecuteParams::build( + targetAddress, ;; usually the uln + $newPfStorage, ;; new priceFeedFeeLib storage + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ) + ); +} + +;; update arb extension +tuple updateArbExtension(cell $mdAddress) impure inline { + (cell $storage, tuple actions) = preamble(); + + (cell $ulnPriceFeed, int targetAddress) = $mdAddress.md::MdAddress::deserialize(); + cell $arbExtension = $ulnPriceFeed.PriceFeedFeelib::getArbitrumExtension(); + + cell $newPfStorage = $storage + .PriceFeedCache::getPriceFeedFeeLibStorage() + .PriceFeedFeelib::setArbitrumExtension($arbExtension); + + setContractStorage( + $storage.PriceFeedCache::setPriceFeedFeeLibStorage($newPfStorage) + ); + + ;; call into the proxy to update the priceFeedFeeLib + return _callViaProxy( + actions, + md::ExecuteParams::build( + targetAddress, ;; usually the uln + $newPfStorage, ;; new priceFeedFeeLib storage + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ) + ); +} + +tuple callViaProxy(cell $executeParams) impure inline { + return _callViaProxy(emptyActions(), $executeParams); +} + +tuple setAdmins(cell $addressList) impure inline { + return _setAdmins(emptyActions(), $addressList); +} + +tuple setProxy(cell $setAddress) impure inline { + return _setProxyAddress( + emptyActions(), + $setAddress.cl::get
(md::SetAddress::address) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/interface.fc new file mode 100644 index 00000000..52069541 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/interface.fc @@ -0,0 +1,7 @@ +;;; ==========================OPCODES===================================== + +const int PriceFeedCache::OP::UPDATE_PRICE = "PriceFeedCache::OP::UPDATE_PRICE"c; +const int PriceFeedCache::OP::UPDATE_NATIVE_PRICE = "PriceFeedCache::OP::UPDATE_NATIVE_PRICE"c; +const int PriceFeedCache::OP::UPDATE_OP_PRICES = "PriceFeedCache::OP::UPDATE_OP_PRICES"c; +const int PriceFeedCache::OP::UPDATE_OP_NATIVE_PRICES = "PriceFeedCache::OP::UPDATE_OP_NATIVE_PRICES"c; +const int PriceFeedCache::OP::UPDATE_ARB_EXTENSION = "PriceFeedCache::OP::UPDATE_ARB_EXTENSION"c; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/main.fc new file mode 100644 index 00000000..f67cce70 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/main.fc @@ -0,0 +1,31 @@ +#include "../../protocol/core/abstract/protocolMain.fc"; + +#include "handler.fc"; +#include "interface.fc"; + + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == PriceFeedCache::OP::UPDATE_PRICE) { + return updatePrice($md); + } elseif (op == PriceFeedCache::OP::UPDATE_NATIVE_PRICE) { + return updateNativePrice($md); + } elseif (op == PriceFeedCache::OP::UPDATE_OP_PRICES) { + return updateOpPrices($md); + } elseif (op == PriceFeedCache::OP::UPDATE_OP_NATIVE_PRICES) { + return updateOpNativePrices($md); + } elseif (op == PriceFeedCache::OP::UPDATE_ARB_EXTENSION) { + return updateArbExtension($md); + } elseif (op == Worker::OP::CALL_VIA_PROXY) { + return callViaProxy($md); + } elseif (op == Worker::OP::SET_ADMINS) { + return setAdmins($md); + } elseif (op == Worker::OP::CLAIM_TON) { + return claimTon($md); + } elseif (op == Worker::OP::SET_PROXY) { + return setProxy($md); + } elseif (op == Proxy::OP::HANDLE_CALLBACK) { + return emptyActions(); + } + throw(BaseInterface::ERROR::invalidOpcode); + return empty_tuple(); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/storage.fc new file mode 100644 index 00000000..e2b9b4e0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/storage.fc @@ -0,0 +1,38 @@ + #include "../../funC++/classlib.fc"; + +#include "../core/workerCoreStorage.fc"; + +#include "../../protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/storage.fc"; + +;; required object name +const int PriceFeedCache::NAME = "pfCache"u; + +;; field names +const int PriceFeedCache::workerCoreStorage = 0; +const int PriceFeedCache::priceFeedFeeLibStorage = 1; +const int PriceFeedCache::dstEid = 2; ;; sharding key, unused in actual logic + +;; @owner manager +cell PriceFeedCache::New(cell admins, int version, cell $priceFeedFeeLib, int dstEid) impure inline method_id { + return cl::declare( + PriceFeedCache::NAME, + unsafeTuple([ + [cl::t::objRef, WorkerCoreStorage::New(admins, NULLADDRESS, version)], ;; PriceFeedCache::workerCoreStorage + [cl::t::objRef, $priceFeedFeeLib], ;; PriceFeedCache::priceFeedFeeLibStorage + [cl::t::uint32, dstEid] ;; PriceFeedCache::dstEid + ]) + ); +} + +cell PriceFeedCache::getPriceFeedFeeLibStorage(cell $self) impure inline { + return $self.cellPreloadRefAt(1); +} + +cell PriceFeedCache::setPriceFeedFeeLibStorage(cell $self, cell $priceFeedFeeLibStorage) impure inline { + slice selfSlice = $self.begin_parse(); + return begin_cell() + .store_slice(selfSlice.scutfirst(_HEADER_WIDTH, 1)) ;; header + refs[0] + .store_ref($priceFeedFeeLibStorage) ;; new refs[1] + .store_uint32(selfSlice.preloadUint32At(_HEADER_WIDTH)) ;; dstEid + .end_cell(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/main.fc new file mode 100644 index 00000000..d80d84c7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/main.fc @@ -0,0 +1,308 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../core/interface.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../workers/core/tests/workerStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../funC++/handlerCore.fc"; + +#include "../../../../tests/consts.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "priceFeedCache"; } + +cell createContractStorage() impure { + setContractStorage( + PriceFeedCache::New( + AddressList::serialize(unsafeTuple([ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS])), + 0, + MOCK_DEFAULT_PRICE_FEED_STORAGE(), + DST_EID + ) + ); + + setContractStorage( + getContractStorage().setCoreStorage( + getCoreStorage().cl::set( + WorkerCoreStorage::proxy, + PROXY_ADDRESS + ) + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { } + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) updatePrice::success::basic(cell $storage) impure { + cell $newPfStorage = $storage + .cl::get(PriceFeedCache::priceFeedFeeLibStorage) + .cl::set(PriceFeedFeelib::priceRatio, CUSTOM_PRICE_RATIO) + .cl::set(PriceFeedFeelib::gasPriceInRemoteUnit, CUSTOM_GAS_PRICE_IN_UNIT) + .cl::set(PriceFeedFeelib::gasPerByte, CUSTOM_GAS_PER_BYTE); + + cell $expectedStorage = $storage.cl::set( + PriceFeedCache::priceFeedFeeLibStorage, + $newPfStorage + ); + + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + $newPfStorage, + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ); + + return test::handler::shouldPass( + updatePrice, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS), + unsafeTuple([ + 0, + _newAction( + PROXY_ADDRESS, + Proxy::OP::CALL_CONTRACT, + $executeParams + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) updateNativePrice::success::basic(cell $storage) impure { + cell $newPfStorage = $storage + .cl::get(PriceFeedCache::priceFeedFeeLibStorage) + .cl::set(PriceFeedFeelib::nativePriceUsd, CUSTOM_NATIVE_PRICE_USD); + + cell $expectedStorage = $storage.cl::set( + PriceFeedCache::priceFeedFeeLibStorage, + $newPfStorage + ); + + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + $newPfStorage, + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ); + + return test::handler::shouldPass( + updateNativePrice, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS), + unsafeTuple([ + 0, + _newAction( + PROXY_ADDRESS, + Proxy::OP::CALL_CONTRACT, + $executeParams + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) updateOpPrices::success::basic(cell $storage) impure { + setContractStorage( + $storage.cl::set( + PriceFeedCache::priceFeedFeeLibStorage, + MOCK_DEFAULT_PRICE_FEED_STORAGE_OP() + ) + ); + $storage = getContractStorage(); + + cell $newOpExtension = $storage + .cl::get(PriceFeedCache::priceFeedFeeLibStorage) + .cl::get(PriceFeedFeelib::optimismExtension) + .cl::set(PriceFeedFeelib::priceRatio, CUSTOM_OP_PRICE_RATIO) + .cl::set(PriceFeedFeelib::gasPriceInRemoteUnit, CUSTOM_OP_GAS_PRICE_IN_UNIT) + .cl::set(PriceFeedFeelib::gasPerByte, CUSTOM_OP_GAS_PER_BYTE); + + cell $newPfStorage = $storage + .cl::get(PriceFeedCache::priceFeedFeeLibStorage) + .cl::set(PriceFeedFeelib::priceRatio, CUSTOM_PRICE_RATIO) + .cl::set(PriceFeedFeelib::gasPriceInRemoteUnit, CUSTOM_GAS_PRICE_IN_UNIT) + .cl::set(PriceFeedFeelib::gasPerByte, CUSTOM_GAS_PER_BYTE) + .cl::set(PriceFeedFeelib::optimismExtension, $newOpExtension); + + cell $expectedStorage = $storage.cl::set( + PriceFeedCache::priceFeedFeeLibStorage, + $newPfStorage + ); + + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + $newPfStorage, + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ); + + return test::handler::shouldPass( + updateOpPrices, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_OP(), ARBITRARY_ADDRESS), + unsafeTuple([ + 0, + _newAction( + PROXY_ADDRESS, + Proxy::OP::CALL_CONTRACT, + $executeParams + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) updateOpNativePrices::success::basic(cell $storage) impure { + setContractStorage( + $storage.cl::set( + PriceFeedCache::priceFeedFeeLibStorage, + MOCK_DEFAULT_PRICE_FEED_STORAGE_OP() + ) + ); + $storage = getContractStorage(); + + cell $newOpExtension = $storage + .cl::get(PriceFeedCache::priceFeedFeeLibStorage) + .cl::get(PriceFeedFeelib::optimismExtension) + .cl::set(PriceFeedFeelib::nativePriceUsd, CUSTOM_OP_NATIVE_PRICE_USD); + + cell $newPfStorage = $storage + .cl::get(PriceFeedCache::priceFeedFeeLibStorage) + .cl::set(PriceFeedFeelib::nativePriceUsd, CUSTOM_NATIVE_PRICE_USD) + .cl::set(PriceFeedFeelib::optimismExtension, $newOpExtension); + + cell $expectedStorage = $storage.cl::set( + PriceFeedCache::priceFeedFeeLibStorage, + $newPfStorage + ); + + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + $newPfStorage, + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ); + + return test::handler::shouldPass( + updateOpNativePrices, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_OP(), ARBITRARY_ADDRESS), + unsafeTuple([ + 0, + _newAction( + PROXY_ADDRESS, + Proxy::OP::CALL_CONTRACT, + $executeParams + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) updateArbExtension::success::basic(cell $storage) impure { + setContractStorage( + $storage.cl::set( + PriceFeedCache::priceFeedFeeLibStorage, + MOCK_DEFAULT_PRICE_FEED_STORAGE_ARB() + ) + ); + $storage = getContractStorage(); + + cell $newArbExtension = $storage + .cl::get(PriceFeedCache::priceFeedFeeLibStorage) + .cl::get(PriceFeedFeelib::arbitrumExtension) + .cl::set(ArbitrumPriceFeedExtension::gasPerL2Tx, CUSTOM_ARB_GAS_PER_L2_TX) + .cl::set(ArbitrumPriceFeedExtension::gasPerL1CallDataByte, CUSTOM_ARB_GAS_PER_L1_CALL_DATA_BYTE); + + cell $newPfStorage = $storage + .cl::get(PriceFeedCache::priceFeedFeeLibStorage) + .cl::set(PriceFeedFeelib::arbitrumExtension, $newArbExtension); + + cell $expectedStorage = $storage.cl::set( + PriceFeedCache::priceFeedFeeLibStorage, + $newPfStorage + ); + + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + $newPfStorage, + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ); + + return test::handler::shouldPass( + updateArbExtension, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_ARB(), ARBITRARY_ADDRESS), + unsafeTuple([ + 0, + _newAction( + PROXY_ADDRESS, + Proxy::OP::CALL_CONTRACT, + $executeParams + ) + ]), + $expectedStorage, + txnContext + ); +} + +(int, slice) callViaProxy::success::updateWorkerFeeLib(cell $storage) impure { + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + $storage, + 0, + Uln::OP::SET_WORKER_FEELIB_STORAGE, + NULLADDRESS + ); + + return test::handler::shouldPass( + callViaProxy, + $executeParams, + unsafeTuple([ + 0, + _newAction( + PROXY_ADDRESS, + Proxy::OP::CALL_CONTRACT, + $executeParams + ) + ]), + $storage, + txnContext + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + ;; update price + .tpush([updatePrice::success::basic, "updatePrice::success::basic"]) + ;; update native price + .tpush([updateNativePrice::success::basic, "updateNativePrice::success::basic"]) + ;; update op prices + .tpush([updateOpPrices::success::basic, "updateOpPrices::success::basic"]) + ;; update op native prices + .tpush([updateOpNativePrices::success::basic, "updateOpNativePrices::success::basic"]) + ;; update arb extension + .tpush([updateArbExtension::success::basic, "updateArbExtension::success::basic"]) + ;; call via proxy + .tpush([callViaProxy::success::updateWorkerFeeLib, "callViaProxy::success::updateWorkerFeeLib"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/permissions.fc new file mode 100644 index 00000000..480c6847 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/permissions.fc @@ -0,0 +1,308 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../workers/core/tests/workerStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/handlerCore.fc"; + +#include "../../../../tests/consts.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "PricefeedCache::Permissions"; } + +cell createContractStorage() impure { + setContractStorage( + PriceFeedCache::New( + AddressList::serialize(unsafeTuple([ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS])), + 0, + MOCK_DEFAULT_PRICE_FEED_STORAGE(), + DST_EID + ) + ); + + setContractStorage( + getContractStorage().setCoreStorage( + getCoreStorage().cl::set( + WorkerCoreStorage::proxy, + PROXY_ADDRESS + ) + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { } + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) checkPermissions::updatePrice::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_PRICE, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updatePrice::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_PRICE, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updatePrice::fail::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + PriceFeedCache::OP::UPDATE_PRICE, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateNativePrice::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_NATIVE_PRICE, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateNativePrice::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_NATIVE_PRICE, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateNativePrice::fail::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + PriceFeedCache::OP::UPDATE_NATIVE_PRICE, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateOpPrices::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_OP_PRICES, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateOpPrices::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_OP_PRICES, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateOpPrices::fail::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + PriceFeedCache::OP::UPDATE_OP_PRICES, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateOpNativePrices::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_OP_NATIVE_PRICES, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateOpNativePrices::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_OP_NATIVE_PRICES, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateOpNativePrices::fail::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + PriceFeedCache::OP::UPDATE_OP_NATIVE_PRICES, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateArbExtension::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_ARB_EXTENSION, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateArbExtension::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + PriceFeedCache::OP::UPDATE_ARB_EXTENSION, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::updateArbExtension::fail::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + PriceFeedCache::OP::UPDATE_ARB_EXTENSION, + md::MdAddress::New(MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT(), ARBITRARY_ADDRESS) + ); +} + +(int, slice) checkPermissions::callViaProxy::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CALL_VIA_PROXY, + MOCK_RANDOM_EXECUTE_PARAMS() + ); +} + +(int, slice) checkPermissions::callViaProxy::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CALL_VIA_PROXY, + MOCK_RANDOM_EXECUTE_PARAMS() + ); +} + +(int, slice) checkPermissions::callViaProxy::fail::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::CALL_VIA_PROXY, + MOCK_RANDOM_EXECUTE_PARAMS() + ); +} + +(int, slice) checkPermissions::setAdmins::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_ADMINS, + AddressList::serialize(unsafeTuple([ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS])) + ); +} + +(int, slice) checkPermissions::setAdmins::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_ADMINS, + AddressList::serialize(unsafeTuple([ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS])) + ); +} + +(int, slice) checkPermissions::setAdmins::fail::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::SET_ADMINS, + AddressList::serialize(unsafeTuple([ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS])) + ); +} + +(int, slice) checkPermissions::claimTon::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON, + MOCK_COINS_AMOUNT() + ); +} + +(int, slice) checkPermissions::claimTon::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON, + MOCK_COINS_AMOUNT() + ); +} + +(int, slice) checkPermissions::claimTon::fail::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::CLAIM_TON, + MOCK_COINS_AMOUNT() + ); +} + +(int, slice) checkPermissions::setProxy::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_PROXY, + MOCK_SET_ADDRESS() + ); +} + +(int, slice) checkPermissions::setProxy::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_PROXY, + MOCK_SET_ADDRESS() + ); +} + +(int, slice) checkPermissions::setProxy::fail::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::SET_PROXY, + MOCK_SET_ADDRESS() + ); +} + +(int, slice) checkPermissions::handleCallback::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + Proxy::OP::HANDLE_CALLBACK, + MOCK_CALLBACK_DATA() + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([checkPermissions::updatePrice::success::admin1, "checkPermissions::updatePrice::success::admin1"]) + .tpush([checkPermissions::updatePrice::success::admin2, "checkPermissions::updatePrice::success::admin2"]) + .tpush([checkPermissions::updatePrice::fail::notAdmin, "checkPermissions::updatePrice::fail::notAdmin"]) + .tpush([checkPermissions::updateNativePrice::success::admin1, "checkPermissions::updateNativePrice::success::admin1"]) + .tpush([checkPermissions::updateNativePrice::success::admin2, "checkPermissions::updateNativePrice::success::admin2"]) + .tpush([checkPermissions::updateNativePrice::fail::notAdmin, "checkPermissions::updateNativePrice::fail::notAdmin"]) + .tpush([checkPermissions::updateOpPrices::success::admin1, "checkPermissions::updateOpPrices::success::admin1"]) + .tpush([checkPermissions::updateOpPrices::success::admin2, "checkPermissions::updateOpPrices::success::admin2"]) + .tpush([checkPermissions::updateOpPrices::fail::notAdmin, "checkPermissions::updateOpPrices::fail::notAdmin"]) + .tpush([checkPermissions::updateOpNativePrices::success::admin1, "checkPermissions::updateOpNativePrices::success::admin1"]) + .tpush([checkPermissions::updateOpNativePrices::success::admin2, "checkPermissions::updateOpNativePrices::success::admin2"]) + .tpush([checkPermissions::updateOpNativePrices::fail::notAdmin, "checkPermissions::updateOpNativePrices::fail::notAdmin"]) + .tpush([checkPermissions::updateArbExtension::success::admin1, "checkPermissions::updateArbExtension::success::admin1"]) + .tpush([checkPermissions::updateArbExtension::success::admin2, "checkPermissions::updateArbExtension::success::admin2"]) + .tpush([checkPermissions::updateArbExtension::fail::notAdmin, "checkPermissions::updateArbExtension::fail::notAdmin"]) + .tpush([checkPermissions::callViaProxy::success::admin1, "checkPermissions::callViaProxy::success::admin1"]) + .tpush([checkPermissions::callViaProxy::success::admin2, "checkPermissions::callViaProxy::success::admin2"]) + .tpush([checkPermissions::callViaProxy::fail::notAdmin, "checkPermissions::callViaProxy::fail::notAdmin"]) + .tpush([checkPermissions::setAdmins::success::admin1, "checkPermissions::setAdmins::success::admin1"]) + .tpush([checkPermissions::setAdmins::success::admin2, "checkPermissions::setAdmins::success::admin2"]) + .tpush([checkPermissions::setAdmins::fail::notAdmin, "checkPermissions::setAdmins::fail::notAdmin"]) + .tpush([checkPermissions::claimTon::success::admin1, "checkPermissions::claimTon::success::admin1"]) + .tpush([checkPermissions::claimTon::success::admin2, "checkPermissions::claimTon::success::admin2"]) + .tpush([checkPermissions::claimTon::fail::notAdmin, "checkPermissions::claimTon::fail::notAdmin"]) + .tpush([checkPermissions::setProxy::success::admin1, "checkPermissions::setProxy::success::admin1"]) + .tpush([checkPermissions::setProxy::success::admin2, "checkPermissions::setProxy::success::admin2"]) + .tpush([checkPermissions::setProxy::fail::notAdmin, "checkPermissions::setProxy::fail::notAdmin"]) + .tpush([checkPermissions::handleCallback::success::basic, "checkPermissions::handleCallback::success::basic"]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/serde.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/serde.fc new file mode 100644 index 00000000..866db929 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/priceFeedCache/tests/serde.fc @@ -0,0 +1,59 @@ +#include "../storage.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/utils.fc"; + +#include "../../../../tests/baseSerdeTest.fc"; +#include "../../../../tests/consts.fc"; +#include "../../../../tests/mocks.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "PriceFeedCache Serde"; } + +;; PriceFeedCache: Has 1 getter +;; Has 1 setter +(int, slice) Serde::PriceFeedCache::getPriceFeedFeeLibStorage(cell $self) impure { + int version = 1; + cell $priceFeedCache = PriceFeedCache::New( + MOCK_ADMIN_WORKER_LIST(), + version, + MOCK_DEFAULT_PRICE_FEED_STORAGE(), + DST_EID + ); + + return test::getRef::equal( + $priceFeedCache, + PriceFeedCache::getPriceFeedFeeLibStorage, + PriceFeedCache::priceFeedFeeLibStorage + ); +} + +(int, slice) Serde::PriceFeedCache::setPriceFeedFeeLibStorage(cell $unused) impure { + int version = 1; + + cell $priceFeedCache = PriceFeedCache::New( + MOCK_ADMIN_WORKER_LIST(), + version, + MOCK_DEFAULT_PRICE_FEED_STORAGE(), + DST_EID + ); + + cell $actualPriceFeedFeeLibStorage = $priceFeedCache.PriceFeedCache::setPriceFeedFeeLibStorage( + MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT() + ); + + cell $expectedPriceFeedCache = $priceFeedCache.cl::set( + PriceFeedCache::priceFeedFeeLibStorage, + MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT() + ); + + return test::set::equal($expectedPriceFeedCache, $actualPriceFeedFeeLibStorage); +} + + +tuple baseTest::getTests() impure { + return empty_tuple() + .tpush([Serde::PriceFeedCache::getPriceFeedFeeLibStorage, "Serde::PriceFeedCache::getPriceFeedFeeLibStorage"]) + .tpush([Serde::PriceFeedCache::setPriceFeedFeeLibStorage, "Serde::PriceFeedCache::setPriceFeedFeeLibStorage"]) + ; +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/handler.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/handler.fc new file mode 100644 index 00000000..5ce71991 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/handler.fc @@ -0,0 +1,84 @@ +#include "../core/abstract/workerHandler.fc"; +#include "../core/interface.fc"; + +#include "../../classes/msgdata/MdAddress.fc"; +#include "../../classes/msgdata/Bool.fc"; + +#include "../../protocol/msglibs/ultralightnode/uln/interface.fc"; + +#include "./interface.fc"; +#include "./storage.fc"; + +;;; ==========================PERMISSION FUNCTIONS================================= + +;; this is special in proxy, only the people in the address book can call 'callContract' +() _checkPermissions(int op, cell $md) impure inline { + if ( + (op == Worker::OP::SET_ADMINS) + | (op == Proxy::OP::CALL_CONTRACT) + | (op == Proxy::OP::EMIT_EVENT) + | (op == Worker::OP::CLAIM_TON) + | (op == Proxy::OP::TOGGLE_CALLBACK) + ) { + return assertAdmin(); + } + return (); +} + +;;; ==========================HANDLERS===================================== + +tuple setAdmins(cell $addressList) impure inline { + return _setAdmins(emptyActions(), $addressList); +} + +tuple claimTon(cell $claimTon) impure inline { + return _claimTon(emptyActions(), $claimTon); +} + +tuple callContract(cell $executeParams) impure inline { + return _callContract(emptyActions(), $executeParams); +} + +tuple emitEvent(cell $event) impure inline { + (cell $storage, tuple actions) = preamble(); + + actions~pushAction( + $event.cl::get(action::event::topic), + $event.cl::get(action::event::body) + ); + + return actions; +} + +tuple callback(cell $md) impure inline { + (cell $storage, tuple actions) = preamble(); + + if ($storage.cl::get(Proxy::callbackEnabled)) { + actions~pushAction( + getLatestAdmin(), + Proxy::OP::HANDLE_CALLBACK, + md::ExecuteParams::New( + getCaller(), + $md, + 0, + getOpcode(), + NULLADDRESS + ) + ); + } + + return actions; +} + +tuple toggleCallback(cell $boolMd) impure inline { + (cell $storage, tuple actions) = preamble(); + + setContractStorage( + $storage.cl::set( + Proxy::callbackEnabled, + $boolMd.cl::get(md::Bool::bool) + ) + ); + + return actions; +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/interface.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/interface.fc new file mode 100644 index 00000000..1823047f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/interface.fc @@ -0,0 +1,12 @@ +;;; ==========================OPCODES===================================== +const int Proxy::OP::CALL_CONTRACT = "Proxy::OP::CALL_CONTRACT"c; +const int Proxy::OP::EMIT_EVENT = "Proxy::OP::EMIT_EVENT"c; + +;; this is the opcode that we will "send out" of proxy to specify the callback +;; note that every other opcode that's namespaced by some contract is handled by the contract itself +;; but in this case, this opcode is handled by the target contract +const int Proxy::OP::HANDLE_CALLBACK = "Proxy::OP::HANDLE_CALLBACK"c; + +const int Proxy::OP::TOGGLE_CALLBACK = "Proxy::OP::TOGGLE_CALLBACK"c; + +const int Proxy::ERROR::NOT_LATEST_ADMIN = 1919; diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/main.fc new file mode 100644 index 00000000..b10b277f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/main.fc @@ -0,0 +1,19 @@ +#include "../../protocol/core/abstract/protocolMain.fc"; + +#include "handler.fc"; +#include "interface.fc"; + +tuple _executeOpcode(int op, cell $md) impure inline { + if (op == Worker::OP::SET_ADMINS) { + return setAdmins($md); + } elseif (op == Worker::OP::CLAIM_TON) { + return claimTon($md); + } elseif (op == Proxy::OP::CALL_CONTRACT) { + return callContract($md); + } elseif (op == Proxy::OP::TOGGLE_CALLBACK) { + return toggleCallback($md); + } elseif (op == Proxy::OP::EMIT_EVENT) { + return emitEvent($md); + } + return callback($md); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/storage.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/storage.fc new file mode 100644 index 00000000..3217c80d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/storage.fc @@ -0,0 +1,21 @@ +#include "../../funC++/classlib.fc"; + +#include "../core/workerCoreStorage.fc"; + +;; required object name +const int Proxy::NAME = "pfProxy"u; + +;; field names +const int Proxy::workerCoreStorage = 0; ;; = WORKER_CORE_STORAGE_IDX +const int Proxy::callbackEnabled = 1; + +;; @owner manager +cell Proxy::New(cell admins, int version) impure inline method_id { + return cl::declare( + Proxy::NAME, + unsafeTuple([ + [cl::t::objRef, WorkerCoreStorage::New(admins, NULLADDRESS, version)], ;; Proxy::workerCoreStorage + [cl::t::bool, false] ;; Proxy::callbackEnabled + ]) + ); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/tests/main.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/tests/main.fc new file mode 100644 index 00000000..c6f82b93 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/tests/main.fc @@ -0,0 +1,211 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; +#include "../../core/interface.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../workers/core/tests/workerStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/handlerCore.fc"; + +#include "../../../classes/msgdata/Bool.fc"; + +#include "../../../../tests/consts.fc"; +#include "../../../funC++/actions/event.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Proxy"; } + +cell createContractStorage() impure { + setContractStorage(Proxy::New( + AddressList::serialize(unsafeTuple([ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS])), 0) + ); + return getContractStorage(); +} + +() _createInitializedStorage() impure { } + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + +(int, slice) claimTon::success::basic() impure { + return test::handler::shouldPass( + claimTon, + md::ClaimTon::New(MOCK_CLAIM_AMOUNT, ARBITRARY_ADDRESS), + unsafeTuple([ + MOCK_CLAIM_AMOUNT, + _newAction( + ARBITRARY_ADDRESS, + MOCK_CLAIM_AMOUNT, + MOCK_CLAIM_AMOUNT + ), + _newAction( + Worker::event::CLAIMED_TON, + md::ClaimTon::New(MOCK_CLAIM_AMOUNT, ARBITRARY_ADDRESS) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) claimTon::revert::lessThanOneTonRemaining() impure { + int claimAmount = getInitialContractBalance() - ONE_TON + 1; + return test::handler::shouldFail( + claimTon, + md::ClaimTon::New(claimAmount, ARBITRARY_ADDRESS), + Worker::ERROR::insufficientBalance + ); +} + +(int, slice) setAdmins::success::basic() impure { + cell $admins = AddressList::serialize(unsafeTuple( + [getCaller(), ARBITRARY_ADDRESS] + )); + + return test::handler::shouldPass( + setAdmins, + $admins, + unsafeTuple([ + 0, + _newAction( + Worker::event::ADMINS_SET, + $admins + ) + ]), + getContractStorage().setCoreStorage( + getCoreStorage().cl::set( + WorkerCoreStorage::admins, + $admins + ) + ), + txnContext + ); +} + +(int, slice) callContract::success::basic() impure { + cell $executeParams = md::ExecuteParams::New( + ARBITRARY_ADDRESS, + cl::nullObject(), + 0, + OP::RANDOM, + NULLADDRESS + ); + + return test::handler::shouldPass( + callContract, + $executeParams, + unsafeTuple([ + 0, + _newAction( + ARBITRARY_ADDRESS, + OP::RANDOM, + cl::nullObject() + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) callback::success::callbackEnabled(cell $storage) impure { + cell $md = _getRandomCode(132); + setContractStorage($storage.cl::set(Proxy::callbackEnabled, true)); + + return test::handler::shouldPass( + callback, + $md, + unsafeTuple([ + 0, + _newAction( + getLatestAdmin(), + Proxy::OP::HANDLE_CALLBACK, + md::ExecuteParams::New( + getCaller(), + $md, + 0, + getOpcode(), + NULLADDRESS + ) + ) + ]), + getContractStorage(), + txnContext + ); +} + +(int, slice) callback::success::callbackDisabled(cell $storage) impure { + return test::handler::shouldPass( + callback, + _getRandomCode(132), + emptyActions(), + $storage, + txnContext + ); +} + +(int, slice) toggleCallback::success::makeItTrue(cell $storage) impure { + return test::handler::shouldPass( + toggleCallback, + md::Bool::New(true), + unsafeTuple([0]), + $storage.cl::set(Proxy::callbackEnabled, true), + txnContext + ); +} + +(int, slice) toggleCallback::success::makeItFalse(cell $storage) impure { + setContractStorage( + $storage.cl::set(Proxy::callbackEnabled, false) + ); + + return test::handler::shouldPass( + toggleCallback, + md::Bool::New(false), + emptyActions(), + getContractStorage().cl::set(Proxy::callbackEnabled, false), + txnContext + ); +} + +(int, slice) emitEvent::success::basic() impure { + return test::handler::shouldPass( + emitEvent, + action::event::New(12, _getRandomCode(132), cl::nullObject()), + unsafeTuple([ + 0, + _newAction( + 12, + _getRandomCode(132) + ) + ]), + getContractStorage(), + txnContext + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([initializeTestStorage, "initializeTestStorage"]) + .tpush([claimTon::success::basic, "claimTon::success::basic"]) + .tpush([claimTon::revert::lessThanOneTonRemaining, "claimTon::revert::lessThanOneTonRemaining"]) + .tpush([setAdmins::success::basic, "setAdmins::success::basic"]) + .tpush([callContract::success::basic, "callContract::success::basic"]) + .tpush([callback::success::callbackEnabled, "callback::success::callbackEnabled"]) + .tpush([callback::success::callbackDisabled, "callback::success::callbackDisabled"]) + .tpush([toggleCallback::success::makeItTrue, "toggleCallback::success::makeItTrue"]) + .tpush([toggleCallback::success::makeItFalse, "toggleCallback::success::makeItFalse"]) + .tpush([emitEvent::success::basic, "emitEvent::success::basic"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/tests/permissions.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/tests/permissions.fc new file mode 100644 index 00000000..790344a5 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/src/workers/proxy/tests/permissions.fc @@ -0,0 +1,194 @@ +#include "../handler.fc"; +#include "../interface.fc"; +#include "../storage.fc"; + +#include "../../../../tests/baseContractTest.fc"; +#include "../../../workers/core/tests/workerStorageTestUtils.fc"; +#include "../../../../tests/mocks.fc"; + +#include "../../../funC++/baseInterface.fc"; +#include "../../../funC++/classlib.fc"; +#include "../../../funC++/txnContext.fc"; +#include "../../../funC++/constants.fc"; +#include "../../../funC++/handlerCore.fc"; + +#include "../../../../tests/consts.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== + +slice _testName() { return "Proxy::Permissions"; } + +cell createContractStorage() impure { + setContractStorage( + Proxy::New( + AddressList::serialize(unsafeTuple( + [ADMIN_ONE_ADDRESS, ADMIN_TWO_ADDRESS] + )), + 0 + ) + ); + + return getContractStorage(); +} + +() _createInitializedStorage() impure { } + +int _callCheckPermissions(int op, cell $md) impure { + checkPermissions(op, $md); + return 0; +} + +;;; ===============================TESTS========================================= + + +(int, slice) checkPermissions::callContract::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Proxy::OP::CALL_CONTRACT, + MOCK_DEFAULT_PRICE_FEED_STORAGE() + ); +} + +(int, slice) checkPermissions::callContract::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Proxy::OP::CALL_CONTRACT, + MOCK_DEFAULT_PRICE_FEED_STORAGE() + ); +} + +(int, slice) checkPermissions::callContract::notRegistered(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Proxy::OP::CALL_CONTRACT, + MOCK_DEFAULT_PRICE_FEED_STORAGE() + ); +} + +(int, slice) checkPermissions::claimTon::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTon::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::CLAIM_TON, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::claimTon::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::CLAIM_TON, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdmins::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_ADMINS, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdmins::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Worker::OP::SET_ADMINS, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::setAdmins::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Worker::OP::SET_ADMINS, + cl::nullObject() + ); +} + + +(int, slice) checkPermissions::callback::success::basic(cell $storage) impure { + spoofCaller(ARBITRARY_ADDRESS); + return test::permissions::shouldPass( + OP::RANDOM, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::toggleCallback::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Proxy::OP::TOGGLE_CALLBACK, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::toggleCallback::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Proxy::OP::TOGGLE_CALLBACK, + cl::nullObject() + ); +} + + +(int, slice) checkPermissions::toggleCallback::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Proxy::OP::TOGGLE_CALLBACK, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::emitEvent::success::admin1(cell $storage) impure { + spoofCaller(ADMIN_ONE_ADDRESS); + return test::permissions::shouldPass( + Proxy::OP::EMIT_EVENT, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::emitEvent::success::admin2(cell $storage) impure { + spoofCaller(ADMIN_TWO_ADDRESS); + return test::permissions::shouldPass( + Proxy::OP::EMIT_EVENT, + cl::nullObject() + ); +} + +(int, slice) checkPermissions::emitEvent::revert::notAdmin(cell $storage) impure { + spoofCaller(ATTACKER_ADDRESS); + return test::permissions::shouldFail( + Proxy::OP::EMIT_EVENT, + cl::nullObject() + ); +} + +tuple baseTest::getTests() impure { + return unsafeTuple( + empty_tuple() + .tpush([checkPermissions::callContract::success::admin1, "checkPermissions::callContract::success::admin1"]) + .tpush([checkPermissions::callContract::success::admin2, "checkPermissions::callContract::success::admin2"]) + .tpush([checkPermissions::callContract::notRegistered, "checkPermissions::callContract::notRegistered"]) + .tpush([checkPermissions::claimTon::success::admin1, "checkPermissions::claimTON::success::admin1"]) + .tpush([checkPermissions::claimTon::success::admin2, "checkPermissions::claimTON::success::admin2"]) + .tpush([checkPermissions::claimTon::revert::notAdmin, "checkPermissions::claimTON::revert::notAdmin"]) + .tpush([checkPermissions::setAdmins::success::admin1, "checkPermissions::setAdmins::success::admin1"]) + .tpush([checkPermissions::setAdmins::success::admin2, "checkPermissions::setAdmins::success::admin2"]) + .tpush([checkPermissions::setAdmins::revert::notAdmin, "checkPermissions::setAdmins::revert::notAdmin"]) + .tpush([checkPermissions::callback::success::basic, "checkPermissions::callback::success::basic"]) + .tpush([checkPermissions::toggleCallback::success::admin1, "checkPermissions::toggleCallback::success::admin1"]) + .tpush([checkPermissions::toggleCallback::success::admin2, "checkPermissions::toggleCallback::success::admin2"]) + .tpush([checkPermissions::toggleCallback::revert::notAdmin, "checkPermissions::toggleCallback::revert::notAdmin"]) + .tpush([checkPermissions::emitEvent::success::admin1, "checkPermissions::emitEvent::success::admin1"]) + .tpush([checkPermissions::emitEvent::success::admin2, "checkPermissions::emitEvent::success::admin2"]) + .tpush([checkPermissions::emitEvent::revert::notAdmin, "checkPermissions::emitEvent::revert::notAdmin"]) + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/UnitTest.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/UnitTest.ts new file mode 100644 index 00000000..439345b1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/UnitTest.ts @@ -0,0 +1,52 @@ +import { Address, Cell, Contract, ContractProvider, SendMode, Sender, beginCell, contractAddress } from '@ton/core' + +export interface UnitTestConfig {} + +export function testCaseConfigToCell(config: UnitTestConfig): Cell { + return beginCell().endCell() +} + +export class UnitTest implements Contract { + constructor( + readonly address: Address, + readonly init?: { code: Cell; data: Cell } + ) {} + + static createFromAddress(address: Address): UnitTest { + return new UnitTest(address) + } + + static createFromConfig(config: UnitTestConfig, code: Cell, workchain = 0): UnitTest { + const data = testCaseConfigToCell(config) + const init = { code, data } + return new UnitTest(contractAddress(workchain, init), init) + } + + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint): Promise { + await provider.internal(via, { + value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell().endCell(), + }) + } + + async sendTest(provider: ContractProvider, via: Sender, value: bigint, testNum = 0n): Promise { + await provider.internal(via, { + value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + // opcode, query_id, failed_count, success_count + body: beginCell() + .storeUint(testNum, 32) + .storeUint(1, 64) // query_id + .storeUint(BigInt('0x' + via.address!.hash.toString('hex')), 256) + .storeCoins(0) + .storeRef(beginCell().endCell()) + .endCell(), + }) + } + + async getTestName(provider: ContractProvider, via: Sender, testNum = 0n): Promise { + const { stack } = await provider.get('get_test_name', [{ type: 'int', value: testNum }]) + return stack.readString() + } +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/baseContractTest.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/baseContractTest.fc new file mode 100644 index 00000000..1b7743fc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/baseContractTest.fc @@ -0,0 +1,204 @@ +#include "./testMain.fc"; +#include "../src/funC++/classlib.fc"; +#include "../src/funC++/stringlib.fc"; +#include "../src/funC++/utils.fc"; + +#include "../src/funC++/actions/event.fc"; +#include "../src/funC++/actions/sendJettons.fc"; +#include "../src/funC++/actions/call.fc"; +#include "../src/funC++/actions/deploy.fc"; +#include "../src/funC++/actions/dispatch.fc"; +#include "../src/funC++/actions/payment.fc"; +#include "../src/funC++/actions/RawCall.fc"; + +;;; ===============================INTERFACE FUNCTIONS=========================== +int _callCheckPermissions(int op, cell $md) impure; + +;;; ===============================BASE TEST INTERFACE IMPLEMENTATIONS=========================== + +;; authenticates and initializes a base storage for an LZ contract +() forceAuthenticate(int base_storage_idx) impure; + +cell createInitializedStorage() impure; + +cell baseTest::prepare(tuple args) impure { + return createInitializedStorage(); +} + +;;; ===============================HELPER FUNCTIONS=========================== +() spoofCaller(int address_hashpart) impure inline { + setCaller(address_hashpart); +} + +;;; ===============================TEST HANDLERS=============================== +;; An empty test, when inserted at the top of a test list it allows initialization of storage +;; then returns success. Helps to avoid OOG for big contracts +(int, slice) initializeTestStorage(cell $storage) impure { + return (TEST_SUCCESS, ""); +} + +;; int TEST_SUCCESS or TEST_FAILURE, slice (optional) error_message +(int, slice) test::handler::shouldPass((cell -> tuple) handler, cell $md, tuple expectedActions, cell $expectedStorage, tuple expectedTxnContext) impure { + ;; Checkpoint the current gas meter + int start = get_gas_consumed(); + + ;; Run the actual handler + tuple actions = handler($md); + + ;; Optionally profile gas + if (do_profile) { + ;; Should not inline gasConsumed because the string construction costs gas + int gasConsumed = get_gas_consumed() - start; + ~strdump(base_error_msg.str::concat(" consumed gas: ").str::concatInt(gasConsumed)); + } + + ;; Check the number of actions matches the expected + int numActions = actions.tlen(); + if (numActions != expectedActions.tlen()) { + return ( + TEST_FAILED, + "action length incorrect:" + .str::concatInt(actions.tlen()) + .str::concat(" !== ") + .str::concatInt(expectedActions.tlen()) + ); + } + + if (actions.int_at(0) != expectedActions.int_at(0)) { + return (TEST_FAILED, "action value outflow incorrect"); + } + + ;; Check that each action matches the expected action + int index = 1; + int terminalActionEncountered = false; + tuple terminalIndices = empty_tuple(); + while (index < numActions) { + tuple actualAction = actions.tuple_at(index); + tuple expectedAction = expectedActions.tuple_at(index); + if (actualAction.int_at(0) != expectedAction.int_at(0)) { + test::throwError("action type incorrect: ".str::concatInt(index)); + } + int actionType = actualAction.int_at(0); + int equal = false; + int isCurrentActionTerminal = false; + + if (actionType == action::deploy::NAME) { + terminalIndices = terminalIndices.tpush(index); + equal = action::deploy::equals(actualAction, expectedAction); + isCurrentActionTerminal = true; + } elseif (actionType == action::call::NAME) { + terminalIndices = terminalIndices.tpush(index); + equal = action::call::equals(actualAction, expectedAction); + isCurrentActionTerminal = true; + } elseif (actionType == action::dispatch::NAME) { + equal = action::dispatch::equals(actualAction, expectedAction); + } elseif (actionType == action::payment::NAME) { + equal = action::payment::equals(actualAction, expectedAction); + } elseif (actionType == action::event::NAME) { + equal = action::event::equals(actualAction, expectedAction); + } elseif (actionType == action::sendJettons::NAME) { + equal = action::sendJettons::equals(actualAction, expectedAction); + isCurrentActionTerminal = true; + } elseif (actionType == action::rawCall::NAME) { + equal = action::rawCall::equals(actualAction, expectedAction); + isCurrentActionTerminal = true; + } + ifnot (equal) { + test::throwError("action incorrect: ".str::concatInt(index)); + } + if (terminalActionEncountered & (~ isCurrentActionTerminal)) { + test::throwError("terminal before non-terminal: ".str::concatInt(index)); + } + index += 1; + terminalActionEncountered = terminalActionEncountered | isCurrentActionTerminal; + } + if (terminalIndices.tlen() > 1) { + test::throwError("Multiple terminal actions"); + } + ;; Check that the storage after running the handler matches the expected storage + if ($expectedStorage.cl::hash() != getContractStorage().cl::hash()) { + ~strdump("Checking individual fields"); + int wrongField = compareObjectFields(getContractStorage(), $expectedStorage); + if (wrongField == INVALID_CLASS_MEMBER) { + return ( + TEST_FAILED, + "Storage and expected storage not of the same type" + ); + } elseif (wrongField != -1) { + return ( + TEST_FAILED, + "malformed field ".str::concatInt(wrongField) + ); + } + } + + if (expectedTxnContext.tlen() != txnContext.tlen()) { + return (TEST_FAILED, "malformed txn context"); + } + ;; check context + int index = 0; + while (index < txnContext.tlen()) { + int mismatch = false; + if (txnContext.at(index).is_int()) { + mismatch = (txnContext.int_at(index) != expectedTxnContext.int_at(index)); + } elseif (txnContext.at(index).is_cell()) { + mismatch = (txnContext.cell_at(index).cell_hash() != expectedTxnContext.cell_at(index).cell_hash()); + } elseif (txnContext.at(index).is_slice()) { + mismatch = (~ txnContext.slice_at(index).equal_slice_bits(expectedTxnContext.slice_at(index))); + } else { + mismatch = (~ txnContext.cell_at(index).cl::hash() == expectedTxnContext.cell_at(index).cl::hash()); + } + if (mismatch) { + return (TEST_FAILED, "txn context mismatch at index".str::concatInt(index)); + } + index += 1; + } + + ;; If all checks pass, return success + return (TEST_SUCCESS, ""); +} + +(int, slice) test::handler::shouldFail((cell -> tuple) fn, cell $md, int expected_error) impure { + int failed = false; + try { + if (fn($md).tlen() >= 0) { + failed = true; + return (TEST_FAILED, "test::handler::shouldFail never throws"); + } + } catch(x, n) { + if (n != expected_error) { + return ( + TEST_FAILED, + "actual error: " + .str::concatInt(n) + .str::concat(" != expected: ") + .str::concatInt(expected_error) + ); + } + } + + return (TEST_SUCCESS, ""); +} + +(int, slice) test::permissions::shouldPass(int op, cell $md) impure { + int failed = false; + try { + _callCheckPermissions(op, $md); + } catch(x, n) { + failed = true; + } + return failed ? (TEST_FAILED, "permissions check should not have thrown") : (TEST_SUCCESS, ""); +} + +(int, slice) test::permissions::shouldFail(int op, cell $md) impure { + int failed = false; + try { + _callCheckPermissions(op, $md); + failed = true; + } catch(x, n) { + ;; Catch is a function that executes in its own context + ;; so if you try to return here, it will actually + ;; return execution to the try block, not the caller of test::permissions::shouldFail + } + return failed ? (TEST_FAILED, "permissions check should have thrown") : (TEST_SUCCESS, ""); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/baseSerdeTest.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/baseSerdeTest.fc new file mode 100644 index 00000000..e3c4bc65 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/baseSerdeTest.fc @@ -0,0 +1,117 @@ +#include "./testMain.fc"; +#include "../src/funC++/classlib.fc"; +#include "../src/funC++/stringlib.fc"; + +cell baseTest::prepare(tuple args) impure { + return cl::nullObject(); +} + +;;; ===============================TEST HANDLERS=============================== + +;; int TEST_SUCCESS or TEST_FAILURE, slice (optional) error_message +(int, slice) test::build::equal(cell $newObject, cell $buildObject) impure { + if ($newObject.cl::hash() != $buildObject.cl::hash()) { + ~strdump("::new output does not match ::build"); + int wrongField = compareObjectFields($newObject, $buildObject); + if (wrongField == INVALID_CLASS_MEMBER) { + return ( + TEST_FAILED, + "Object and expected object not of the same type" + ); + } elseif (wrongField != -1) { + return ( + TEST_FAILED, + "malformed field ".str::concatInt(wrongField) + ); + } + } + return (TEST_SUCCESS, ""); +} + +(int, slice) test::getBool::equal(cell $obj, (cell -> int) getter, int field) impure { + int expected = $obj.cl::get(field); + int actual = getter($obj); + if (expected != actual) { + return (TEST_FAILED, "expected: ".str::concatInt(expected).str::concat(" != actual: ").str::concatInt(actual)); + } + return (TEST_SUCCESS, ""); +} + +(int, slice) test::getData::equal(cell $obj, (cell -> int) getter, int field) impure { + int width = _getTypeWidth(typeofField($obj, field)); + int expected = $obj.cl::get(field, width); + int actual = getter($obj); + if (expected != actual) { + return (TEST_FAILED, "expected: ".str::concatInt(expected).str::concat(" != actual: ").str::concatInt(actual)); + } + return (TEST_SUCCESS, ""); +} + +(int, slice) test::getRef::equal(cell $obj, (cell -> cell) getter, int fieldId) impure { + cell expected = $obj.cl::get(fieldId); + cell actual = getter($obj); + if (expected.cell_hash() != actual.cell_hash()) { + return (TEST_FAILED, "expected != actual"); + } + return (TEST_SUCCESS, ""); +} + +(int, slice) test::multiget::equal(cell $object, tuple keys, tuple expected) impure { + tuple actual = empty_tuple(); + int idx = 0; + + while (idx < keys.tlen()) { + int key = keys.int_at(idx); + int typeOfKey = typeofField($object, key); + int fieldBits = _getTypeWidth(typeOfKey); + if (typeOfKey == cl::t::bool) { + actual~tpush($object.cl::get(key)); + } elseif (typeOfKey <= cl::t::uint256) { + actual~tpush($object.cl::get(key, fieldBits)); + } else { + actual~tpush($object.cl::get(key)); + } + idx += 1; + } + + if (expected.tlen() != actual.tlen()) { + return (TEST_FAILED, "expected length: ".str::concatInt(expected.tlen()).str::concat(" != actual length: ").str::concatInt(actual.tlen())); + } + + int idx = 0; + while (idx < expected.tlen()) { + if (expected.at(idx).is_cell() & actual.at(idx).is_cell()) { + if (expected.at(idx).cell_hash() != actual.at(idx).cell_hash()) { + return (TEST_FAILED, "expected cell at idx ".str::concatInt(idx).str::concat(" != actual cell")); + } + } elseif (expected.at(idx).is_int() & actual.at(idx).is_int()) { + if (expected.at(idx) != actual.at(idx)) { + return (TEST_FAILED, "expected: ".str::concatInt(expected.at(idx)).str::concat(" != actual: ").str::concatInt(actual.at(idx))); + } + } else { + return (TEST_FAILED, "type mismatch at idx ".str::concatInt(idx)); + } + idx += 1; + } + + return (TEST_SUCCESS, ""); +} + +(int, slice) test::set::equal(cell $expected, cell $actual) impure { + if ($expected.cl::hash() != $actual.cl::hash()) { + ~strdump("::set output does not match ::get"); + int wrongField = compareObjectFields($expected, $actual); + if (wrongField == INVALID_CLASS_MEMBER) { + return ( + TEST_FAILED, + "Object and expected object not of the same type" + ); + } elseif (wrongField != -1) { + return ( + TEST_FAILED, + "malformed field ".str::concatInt(wrongField) + ); + } + } + return (TEST_SUCCESS, ""); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/consts.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/consts.fc new file mode 100644 index 00000000..9472f89c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/consts.fc @@ -0,0 +1,160 @@ +#include "../src/funC++/constants.fc"; +#include "../src/protocol/msglibs/ultralightnode/uln/interface.fc"; +;;; =============addresses to be used in tests==================== +const int CONTROLLER_ADDRESS = 1968; +const int ENDPOINT_ADDRESS = 1969; +const int CHANNEL_ADDRESS = 1970; +const int ULN_MANAGER_ADDRESS = 1971; +const int ULN_ADDRESS = 1972; +const int ULN_CONNECTION_ADDRESS = 1973; +const int ATTACKER_ADDRESS = 1974; +const int ARBITRARY_ADDRESS = 1975; +const int EXECUTOR = 1976; +const int ADMIN_ONE_ADDRESS = 1977; +const int ADMIN_TWO_ADDRESS = 1978; +const int TENTATIVE_OWNER_ADDRESS = 1979; + +;; Can be used for both packet_send and packet_receive +const int PACKET_ADDRESS = 1980; +;; These are to be used agnostically of send vs receive +const int MSGLIB_MANAGER_ADDRESS = 1981; +const int MSGLIB_ADDRESS = 1982; +const int MSGLIB_CONNECTION_ADDRESS = 1983; +const int SEND_MSGLIB_MANAGER_ADDRESS = 1984; +const int SEND_MSGLIB_ADDRESS = 1985; +const int SEND_MSGLIB_CONNECTION_ADDRESS = 1986; +const int RECEIVE_MSGLIB_MANAGER_ADDRESS = 1987; +const int RECEIVE_MSGLIB_ADDRESS = 1988; +const int RECEIVE_MSGLIB_CONNECTION_ADDRESS = 1989; +const int TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS = 1990; +const int TIMEOUT_RECEIVE_MSGLIB_ADDRESS = 1991; +const int TIMEOUT_RECEIVE_MSGLIB_CONNECTION_ADDRESS = 1992; +const int PROTOCOL_ADMIN_ZRO_WALLET_ADDRESS = 1993; +const int NEW_CONTROLLER_OWNER_ADDRESS = 1994; +const int SRC_OAPP = 1995; +const int DST_OAPP = 1996; +const int PLANNER_ADDRESS = 1997; +const int NEW_WORKER_OWNER_ADDRESS = 1998; +const int PROXY_ADDRESS = 1999; + +;;; =====================non-address constants to be used in tests==================== +const int SRC_EID = 30101; +const int DST_EID = 30102; +const int DST_V1_EID = 102; +const int BAD_EID = 103; + +const int QUORUM = 0; + +const int COUNTER_ID = 104; + +const int INITIAL_DEPOSIT = 10000; + +const int NONCE = 42; +const int FIRST_UNEXECUTED_NONCE = 40; +const int LZ_RECEIVE_PREPARE_GAS = 500000; + +const int MSG_TYPE = 1; + +const int RESERVE_NANOS = 2059; +const int NATIVE_FEE = 100; +const int ZRO_FEE = 101; +const int RENT_SECONDS = 10 * 60; + +const int OAPP_QUOTE_UUID = 122; + +const int OP::RANDOM = 720; + +const int NONCE_BYTE_OFFSET = 1; +const int NONCE_BYTES = 8; +const int GUID_BYTE_OFFSET = 81; +const int GUID_BYTES = 32; + +const int SML_MANAGER_VERSION = 1; + +const int WORKER_ADDRESS = 0xd00d; +const int ADMIN_WORKER_ADDRESS = 0xc001d00d; + +const int MOCK_FEE = 100; + +const int EXECUTION_VALUE_NANOS = 100000000; + +const int NATIVE_DROP_MAX_TOTAL = 1000000; +const int NATIVE_DROP_AMOUNT = 101; + +const int MOCK_ULN_TREASURY_CLAIM_AMOUNT = 2002; +const int MOCK_CLAIM_AMOUNT = 1003; + +const int DEFAULT_MIN_COMMIT_PACKET_GAS = 1000 * 1000; +const int CUSTOM_MIN_COMMIT_PACKET_GAS = DEFAULT_MIN_COMMIT_PACKET_GAS * 2; +const int DEFAULT_WORKER_QUOTE_GAS_LIMIT = 100 * 1000; +const int CUSTOM_WORKER_QUOTE_GAS_LIMIT = DEFAULT_WORKER_QUOTE_GAS_LIMIT * 2; +const int DEFAULT_CONFIRMATIONS = 20; +const int CUSTOM_CONFIRMATIONS = DEFAULT_CONFIRMATIONS * 2; +const int CUSTOM_MAX_MESSAGE_BYTES = 40; +const int DEFAULT_MAX_MESSAGE_BYTES = 4096; + +;; MAX_U8 == (UlnReceiveConfig::CONST::NIL_DVN_COUNT + 1) +const int CUSTOM_REQUIRED_DVN0_ADDRESS = EXECUTOR + MAX_U8; +const int CUSTOM_OPTIONAL_DVN0_ADDRESS = CUSTOM_REQUIRED_DVN0_ADDRESS + MAX_U8; +const int CUSTOM_PRICE_FEED_ADDRESS = 42069; +const int DEFAULT_PRICE_FEED_ADDRESS = CUSTOM_PRICE_FEED_ADDRESS + 1; + +const int DEFAULT_REQUIRED_DVN0_ADDRESS = CUSTOM_OPTIONAL_DVN0_ADDRESS + MAX_U8; +const int DEFAULT_OPTIONAL_DVN0_ADDRESS = DEFAULT_REQUIRED_DVN0_ADDRESS + MAX_U8; +const int NULL_DVN0_ADDRESS = NULLADDRESS; + +const int DEFAULT_EXECUTOR = EXECUTOR + 1; +const int CUSTOM_EXECUTOR = EXECUTOR + 2; + +const int DEFAULT_REQUIRED_DVNS = 2; +const int DEFAULT_OPTIONAL_DVNS = 0; +const int DEFAULT_OPTIONAL_DVN_THRESHOLD = DEFAULT_OPTIONAL_DVNS; + +const int PACKET_HASH = 1234; + +const int RENT_NANOS = 1055; + +const int SEND_ID = 4321; + +const int DEFAULT_PRICE_RATIO = 10; +const int DEFAULT_GAS_PRICE_IN_UNIT = 11; +const int DEFAULT_GAS_PER_BYTE = 12; +const int DEFAULT_NATIVE_PRICE_USD = 13; + +const int TREASURY_FEE_BPS = 100; + +const int CUSTOM_DVN_DEFAULT_FEELIB_NATIVE_FEE = 58823979; +const int CUSTOM_EXECUTOR_DEFAULT_FEELIB_NATIVE_FEE = 58823622; + +const int CUSTOM_DVN_ARB_FEELIB_NATIVE_FEE = 58824172; +const int CUSTOM_EXECUTOR_ARB_FEELIB_NATIVE_FEE = 58823633; + +const int CUSTOM_DVN_OP_FEELIB_NATIVE_FEE = 47619626; +const int CUSTOM_EXECUTOR_OP_FEELIB_NATIVE_FEE = 47619106; + +const int CUSTOM_PRICE_RATIO = 1851851; +const int CUSTOM_GAS_PRICE_IN_UNIT = 36000000000; +const int CUSTOM_GAS_PER_BYTE = 1000; +const int CUSTOM_NATIVE_PRICE_USD = 17; + +const int DEFAULT_ARB_GAS_PER_L2_TX = 171; +const int DEFAULT_ARB_GAS_PER_L1_CALL_DATA_BYTE = 700; + +const int CUSTOM_ARB_GAS_PER_L2_TX = 156; +const int CUSTOM_ARB_GAS_PER_L1_CALL_DATA_BYTE = 913; + +const int DEFAULT_OP_PRICE_RATIO = 111222; +const int DEFAULT_OP_GAS_PRICE_IN_UNIT = 1000050000; +const int DEFAULT_OP_GAS_PER_BYTE = 750; +const int DEFAULT_OP_NATIVE_PRICE_USD = 19; + +const int CUSTOM_OP_PRICE_RATIO = 1951951; +const int CUSTOM_OP_GAS_PRICE_IN_UNIT = 12000000000; +const int CUSTOM_OP_GAS_PER_BYTE = 800; +const int CUSTOM_OP_NATIVE_PRICE_USD = 21; + +const int EXECUTOR_FEELIB_LZ_RECEIVE_BASE_GAS = 100000; +const int EXECUTOR_FEELIB_MULTIPLIER_BPS = 1; +const int EXECUTOR_FEELIB_FLOOR_MARGIN_USD = 1; +const int EXECUTOR_FEELIB_NATIVE_CAP = 1000000000000; +const int EXECUTOR_FEELIB_LZ_COMPOSE_BASE_GAS = 1; \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/globalTestResults.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/globalTestResults.ts new file mode 100644 index 00000000..a29f6b69 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/globalTestResults.ts @@ -0,0 +1 @@ +export const globalTestResults: { [testName: string]: { [key: string]: boolean } } = {} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/mocks.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/mocks.fc new file mode 100644 index 00000000..dab991c8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/mocks.fc @@ -0,0 +1,980 @@ +#include "../src/classes/lz/Path.fc"; +#include "../src/classes/lz/Config.fc"; +#include "../src/classes/lz/Packet.fc"; +#include "../src/classes/lz/EpConfig.fc"; +#include "../src/classes/msgdata/SetSmlManagerConfig.fc"; + +#include "consts.fc"; +#include "../src/funC++/classlib.fc"; +#include "../src/funC++/txnContext.fc"; +#include "../src/classes/lz/MsglibInfo.fc"; +#include "../src/classes/lz/ReceiveEpConfig.fc"; +#include "../src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibInfo.fc"; +#include "../src/classes/lz/SendEpConfig.fc"; +#include "../src/classes/lz/SmlJobAssigned.fc"; +#include "../src/protocol/msglibs/ultralightnode/msgdata/UlnReceiveConfig.fc"; +#include "../src/protocol/msglibs/ultralightnode/msgdata/UlnSendConfig.fc"; +#include "../src/classes/msgdata/OptionsExtended.fc"; +#include "../src/classes/msgdata/LzSend.fc"; +#include "../src/classes/msgdata/CoinsAmount.fc"; +#include "../src/classes/msgdata/MsglibSendCallback.fc"; +#include "../src/classes/msgdata/OptionsV1.fc"; +#include "../src/classes/msgdata/OptionsV2.fc"; +#include "../src/classes/msgdata/PacketId.fc"; +#include "../src/classes/msgdata/SetEpConfig.fc"; +#include "../src/classes/msgdata/SetAddress.fc"; +#include "../src/protocol/channel/interface.fc"; +#include "../src/protocol/msglibs/BytesEncoder.fc"; +#include "../src/protocol/msglibs/ultralightnode/msgdata/Attestation.fc"; +#include "../src/protocol/msglibs/ultralightnode/msgdata/UlnSend.fc"; +#include "../src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/storage.fc"; +#include "../src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/storage.fc"; +#include "../src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/storage.fc"; +#include "../src/protocol/msglibs/ultralightnode/msgdata/UlnVerification.fc"; +#include "../src/protocol/msglibs/ultralightnode/msgdata/UlnWorkerFeelibBytecode.fc"; +#include "../src/protocol/msglibs/ultralightnode/ulnConnection/storage.fc"; +#include "../src/workers/msgdata/ExecuteParams.fc"; +#include "../src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/extensions/ArbitrumPriceFeedExtension.fc"; + +;; fake messagelib storage +cell mockMsglibConnectionStorage(int managerAddress, cell $path) { + return cl::declare( + "mockStore"u, + unsafeTuple([ + [cl::t::objRef, BaseStorage::New(managerAddress)], ;; Unused + [cl::t::objRef, $path] ;; path + ]) + ); +} + +;;; =====================helpers to generate constants for tests==================== +cell MOCK_SEND_PATH() impure { + return lz::Path::New( + SRC_EID, + SRC_OAPP, + DST_EID, + DST_OAPP + ); +} + +cell MOCK_RECEIVE_PATH() impure { + return lz::Path::New( + DST_EID, + DST_OAPP, + SRC_EID, + SRC_OAPP + ); +} + +;; this is the actual message sent by the OApp +;; does not include any headers/protocol overheads +cell MOCK_MESSAGE() inline { + return begin_cell() + .store_uint256("Message"u) + .store_uint64(124) + .end_cell(); +} + +cell MOCK_MESSAGE_MAX_BYTES() inline { + return begin_cell() + .store_uint256("Message greater than maxMsgBytes"u) + .store_uint64(124) + .store_uint64(124) + .end_cell(); +} + +cell MOCK_NONCELESS_PACKET() { + return lz::Packet::nonceless( + MOCK_SEND_PATH(), + MOCK_MESSAGE() + ); +} + +cell MOCK_NONCELESS_PACKET_WITH_MAX_MSG() { + return lz::Packet::nonceless( + MOCK_SEND_PATH(), + MOCK_MESSAGE_MAX_BYTES() + ); +} + +cell MOCK_SEND_PACKET() { + return lz::Packet::New( + MOCK_SEND_PATH(), + MOCK_MESSAGE(), + NONCE + ); +} + +cell MOCK_RECEIVE_PACKET_WITH_NONCE(int nonce) { + return lz::Packet::New( + MOCK_RECEIVE_PATH(), + MOCK_MESSAGE(), + nonce + ); +} + +cell MOCK_RECEIVE_PACKET() { + return MOCK_RECEIVE_PACKET_WITH_NONCE(NONCE); +} + +cell MOCK_RECEIVE_PACKET_WITH_MESSAGE(cell message) { + return lz::Packet::New( + MOCK_RECEIVE_PATH(), + message, + NONCE + ); +} + +cell MOCK_SEND_PACKET_WITH_NONCE(int nonce) { + return lz::Packet::New( + MOCK_SEND_PATH(), + MOCK_MESSAGE(), + nonce + ); +} + +cell MOCK_PACKET_WITH_MESSAGE(cell message) { + return lz::Packet::New( + MOCK_SEND_PATH(), + message, + NONCE + ); +} + +builder _getRandomCodeBuilder(int seed) inline { + return begin_cell().store_uint256(seed); +} + +cell _getRandomCode(int seed) inline { + return _getRandomCodeBuilder(seed).end_cell(); +} + +cell MOCK_CHANNEL_CODE() inline { + return _getRandomCode(1); +} + +cell MOCK_ENDPOINT_CODE() inline { + return _getRandomCode(4); +} + +cell MOCK_MSGLIB_CONNECTION_CODE() inline { + return _getRandomCode(5); +} + +cell MOCK_ZRO_WALLET_CODE() inline { + return _getRandomCode(6); +} + +cell MOCK_ULN_CODE() inline { + return _getRandomCode(7); +} + +cell MOCK_ULN_CONNECTION_CODE() inline { + return _getRandomCode(8); +} + +cell MOCK_EXECUTOR_CODE() inline { + return _getRandomCode(9); +} + +int MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() inline { + return now() + 60; +} + +cell MOCK_WORKER_FEELIB_BYTECODE(int seed) { + return _getRandomCode(seed); +} + +cell MOCK_SINGLE_LEVEL_WORKER_FEELIB_STORAGE() { + return _getRandomCode(10); +} + +cell MOCK_DOUBLE_LEVEL_WORKER_FEELIB_STORAGE() { + return _getRandomCodeBuilder(11).store_ref(_getRandomCode(12)).end_cell(); +} + +cell MOCK_EXTRA_OPTIONS_V1() inline { + return md::OptionsV1::New( + 1, + 2, + DST_OAPP, + 3 + ); +} + +cell MOCK_EXTRA_OPTIONS_V2() inline { + return md::OptionsV2::New( + 4, + 5, + 6, + 7, + DST_OAPP, + 8 + ); +} + +cell MOCK_ENFORCED_OPTIONS_V1() inline { + return md::OptionsV1::New( + 9, + 10, + DST_OAPP, + 11 + ); +} + +cell MOCK_OPTIONS_EXTENDED() inline { + return md::OptionsExtended::New( + DST_EID, + MSG_TYPE, + MOCK_ENFORCED_OPTIONS_V1() + ); +} + +cell MOCK_CALLBACK_DATA() inline { + return begin_cell().store_uint256(200).end_cell(); +} + +cell MOCK_LZ_SEND_WITH_ID(int nonce) { + return md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + MOCK_NONCELESS_PACKET(), + MOCK_CALLBACK_DATA() + ) + .cl::set(md::LzSend::sendRequestId, nonce) + .cl::set(md::LzSend::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); +} + +cell MOCK_LZ_SEND_WITH_MAX_PACKET_AND_ID(int nonce) { + return md::LzSend::New( + NATIVE_FEE, + ZRO_FEE, + MOCK_EXTRA_OPTIONS_V1(), + MOCK_ENFORCED_OPTIONS_V1(), + MOCK_NONCELESS_PACKET_WITH_MAX_MSG(), + MOCK_CALLBACK_DATA() + ) + .cl::set(md::LzSend::sendRequestId, nonce) + .cl::set(md::LzSend::sendMsglibManager, SEND_MSGLIB_MANAGER_ADDRESS) + .cl::set(md::LzSend::sendMsglib, SEND_MSGLIB_ADDRESS) + .cl::set(md::LzSend::sendMsglibConnection, SEND_MSGLIB_CONNECTION_ADDRESS); +} + +cell MOCK_LZ_SEND() { + return MOCK_LZ_SEND_WITH_ID(0); +} + +cell MOCK_LZ_SEND_WITH_MAX_PACKET() { + return MOCK_LZ_SEND_WITH_MAX_PACKET_AND_ID(0); +} + +cell MOCK_EP_CONFIG(int useDefaults) { + return lz::EpConfig::New( + useDefaults, + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + RECEIVE_MSGLIB_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); +} + +cell MOCK_RESOLVED_EP_CONFIG(int useDefaults) method_id { + return lz::EpConfig::NewWithConnection( + useDefaults, + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + SEND_MSGLIB_CONNECTION_ADDRESS, + RECEIVE_MSGLIB_ADDRESS, + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); +} + +cell MOCK_MSG_LIB_INFO(int msgLibAddress) { + return lz::MsglibInfo::New( + msgLibAddress, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage( + msgLibAddress, + MOCK_SEND_PATH() + ) + ); +} + +cell CALCULATED_MOCK_RESOLVED_EP_CONFIG(int useDefaults) method_id { + return lz::EpConfig::NewWithConnection( + useDefaults, + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + lz::MsglibInfo::getMsglibConnectionAddress( + lz::MsglibInfo::New( + SEND_MSGLIB_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage( + SEND_MSGLIB_MANAGER_ADDRESS, + MOCK_SEND_PATH() + ) + ), + MOCK_SEND_PATH() + ), + RECEIVE_MSGLIB_ADDRESS, + lz::MsglibInfo::getMsglibConnectionAddress( + lz::MsglibInfo::New( + RECEIVE_MSGLIB_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage( + RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_SEND_PATH() + ) + ), + MOCK_SEND_PATH() + ), + TIMEOUT_RECEIVE_MSGLIB_ADDRESS, + lz::MsglibInfo::getMsglibConnectionAddress( + lz::MsglibInfo::New( + TIMEOUT_RECEIVE_MSGLIB_ADDRESS, + MOCK_MSGLIB_CONNECTION_CODE(), + mockMsglibConnectionStorage( + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_SEND_PATH() + ) + ), + MOCK_SEND_PATH() + ), + now() + 60 + ); +} + +cell MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_FEES_AND_PAYEES(cell $lzSend, int nativeFee, int zroFee, tuple payees) { + return md::MsglibSendCallback::New( + nativeFee, + zroFee, + $lzSend, + BytesEncoder::build(MOCK_SEND_PACKET()).BytesEncoder::serialize(), + serializePayees( + payees + ), + NONCE_BYTE_OFFSET, + NONCE_BYTES, + GUID_BYTE_OFFSET, + GUID_BYTES, + lz::SmlJobAssigned::New(MOCK_FEE), + Channel::NO_ERROR + ); +} + +cell MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_FEES(cell $lzSend, int nativeFee, int zroFee) { + return md::MsglibSendCallback::New( + nativeFee, + zroFee, + $lzSend, + BytesEncoder::build(MOCK_SEND_PACKET()).BytesEncoder::serialize(), + serializePayees(unsafeTuple([[WORKER_ADDRESS, nativeFee]])), + NONCE_BYTE_OFFSET, + NONCE_BYTES, + GUID_BYTE_OFFSET, + GUID_BYTES, + lz::SmlJobAssigned::New(MOCK_FEE), + Channel::NO_ERROR + ); +} + +cell MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_PAYEES(cell $lzSend, tuple payees) { + return md::MsglibSendCallback::New( + NATIVE_FEE, + ZRO_FEE, + $lzSend, + BytesEncoder::build(MOCK_SEND_PACKET()).BytesEncoder::serialize(), + serializePayees(payees), + NONCE_BYTE_OFFSET, + NONCE_BYTES, + GUID_BYTE_OFFSET, + GUID_BYTES, + lz::SmlJobAssigned::New(MOCK_FEE), + Channel::NO_ERROR + ); +} + +cell MOCK_MSGLIB_SEND_CALLBACK_QUOTE_FAILED_WITH_LZSEND(cell $lzSend) { + return md::MsglibSendCallback::New( + 0, + 0, + $lzSend, + empty_cell(), + empty_cell(), + 0, + 0, + 0, + 0, + cl::nullObject(), + Uln::ErrorCode::WORKER_QUOTE_FAILED + ); +} + +cell MOCK_MSGLIB_SEND_CALLBACK_WITH_FEES(int nativeFee, int zroFee) { + return MOCK_MSGLIB_SEND_CALLBACK_WITH_LZSEND_AND_FEES_AND_PAYEES( + MOCK_LZ_SEND(), + nativeFee, + zroFee, + unsafeTuple([[WORKER_ADDRESS, nativeFee]]) + ); +} + +tuple MOCK_PAYEES(int numPayees) { + int idx = 0; + tuple payees = empty_tuple(); + repeat (numPayees) { + payees = payees.tpush( + [WORKER_ADDRESS, NATIVE_FEE + idx] + ); + idx += 1; + } + return payees; +} + +cell MOCK_MSGLIB_SEND_CALLBACK() { + return MOCK_MSGLIB_SEND_CALLBACK_WITH_FEES(NATIVE_FEE, ZRO_FEE); +} + +cell MOCK_SET_EP_CONFIG_MD(int useDefaults) { + return md::SetEpConfig::New( + useDefaults, + SEND_MSGLIB_MANAGER_ADDRESS, + RECEIVE_MSGLIB_MANAGER_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_MANAGER_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); +} + +cell MOCK_SML_SEND_EP_CONFIG() { + return lz::SendEpConfig::New( + SEND_MSGLIB_MANAGER_ADDRESS, + SEND_MSGLIB_ADDRESS, + SEND_MSGLIB_CONNECTION_ADDRESS + ); +} + +cell MOCK_SML_RECEIVE_EP_CONFIG() { + return lz::ReceiveEpConfig::New( + RECEIVE_MSGLIB_CONNECTION_ADDRESS, + TIMEOUT_RECEIVE_MSGLIB_CONNECTION_ADDRESS, + MOCK_TIMEOUT_RECEIVE_LIB_EXPIRY() + ); +} + +cell _buildSendDvnListCell(int dvnCount, int dvnAddressSeed, int isAdmin) { + ;; Initialize first builder + tuple dvnListBuilder = empty_tuple(); + dvnListBuilder~tpush(begin_cell()); + + ;; Build the list directly + repeat(dvnCount) { + dvnCount -= 1; + ;; Check if we need a new builder (1023 is max bits per cell) + if ((dvnListBuilder.at(dvnListBuilder.tlen() - 1).builder_bits() + 256) > 1023) { + dvnListBuilder~tpush(begin_cell()); + } + + ;; Get current builder and store value + builder curBuilder = dvnListBuilder.at(dvnListBuilder.tlen() - 1); + curBuilder = curBuilder.store_uint256(dvnAddressSeed + dvnCount); + dvnListBuilder = dvnListBuilder.tset(dvnListBuilder.tlen() - 1, curBuilder); + } + + ;; Chain the references + builder ret = dvnListBuilder~tpop(); + while (dvnListBuilder.tlen() > 0) { + builder head = dvnListBuilder~tpop(); + ret = head.store_ref(ret.end_cell()); + } + + return ret.end_cell(); +} + +tuple _buildSendDvnListTuple(int dvnCount, int dvnAddressSeed) { + tuple dvnList = empty_tuple(); + int idx = 0; + repeat(dvnCount) { + dvnList~tpush(dvnAddressSeed + idx); + idx += 1; + } + return dvnList; +} + +cell _buildSendDvnCellWithMultipleRef(int dvnCount, int dvnAddressSeed) { + builder b = begin_cell(); + cell ref1 = begin_cell().store_uint(1234, 256).end_cell(); + + while (dvnCount > 0) { + b = b.store_uint(dvnAddressSeed + dvnCount, 256); + if (dvnCount == 1) { ;; Add ref for testing + b = b.store_ref(ref1).store_ref(ref1); + } + dvnCount -= 1; + } + return b.end_cell(); +} + +cell _buildReceiveDvnList(int dvnCount, int dvnAddressSeed) { + return _buildSendDvnListCell(dvnCount, dvnAddressSeed, false); +} + +tuple _receiveDvnListToTuple(cell dvnListCell) { + tuple dvnList = empty_tuple(); + slice dvnListSlice = dvnListCell.begin_parse(); + int dvnAddress = dvnListSlice~AddressList::next(); + throw_unless(1, dvnAddress > NULLADDRESS); + while (dvnAddress > NULLADDRESS) { + dvnList = dvnList.tpush(dvnAddress); + dvnAddress = dvnListSlice~AddressList::next(); + } + return dvnList; +} + +cell MOCK_CUSTOM_ULN_RECEIVE_CONFIG(int requiredDvnCount, int optionalDvnCount, int optionalDvnThreshold) { + return UlnReceiveConfig::New( + false, + CUSTOM_MIN_COMMIT_PACKET_GAS, + false, + CUSTOM_CONFIRMATIONS, + false, + _buildReceiveDvnList(requiredDvnCount, CUSTOM_REQUIRED_DVN0_ADDRESS), + false, + _buildReceiveDvnList(optionalDvnCount, CUSTOM_OPTIONAL_DVN0_ADDRESS), + optionalDvnThreshold + ); +} + +cell MOCK_DEFAULT_ULN_RECEIVE_CONFIG() { + return UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + DEFAULT_CONFIRMATIONS, + false, + _buildReceiveDvnList(DEFAULT_REQUIRED_DVNS, DEFAULT_REQUIRED_DVN0_ADDRESS), + false, + _buildReceiveDvnList(DEFAULT_OPTIONAL_DVNS, DEFAULT_OPTIONAL_DVN0_ADDRESS), + DEFAULT_OPTIONAL_DVN_THRESHOLD + ); +} + +cell MOCK_DEFAULT_ULN_RECEIVE_CONFIG_WITH_COMMIT_GAS(int commitPacketGas) { + return UlnReceiveConfig::New( + false, + commitPacketGas, + false, + DEFAULT_CONFIRMATIONS, + false, + _buildReceiveDvnList(DEFAULT_REQUIRED_DVNS, DEFAULT_REQUIRED_DVN0_ADDRESS), + false, + _buildReceiveDvnList(DEFAULT_OPTIONAL_DVNS, DEFAULT_OPTIONAL_DVN0_ADDRESS), + DEFAULT_OPTIONAL_DVN_THRESHOLD + ); +} + +cell MOCK_DEFAULT_ULN_RECEIVE_CONFIG_WITH_OPTIONAL_DVNS(int optionalDvnCount, int optionalDvnThreshold) { + return UlnReceiveConfig::New( + false, + DEFAULT_MIN_COMMIT_PACKET_GAS, + false, + DEFAULT_CONFIRMATIONS, + false, + _buildReceiveDvnList(DEFAULT_REQUIRED_DVNS, DEFAULT_REQUIRED_DVN0_ADDRESS), + false, + _buildReceiveDvnList(optionalDvnCount, DEFAULT_OPTIONAL_DVN0_ADDRESS), + optionalDvnThreshold + ); +} + + +cell MOCK_ULN_WORKER_FEELIB_INFO(int workerAddress, int rentNanos) { + return UlnWorkerFeelibInfo::New( + workerAddress, + my_code(), + MOCK_SINGLE_LEVEL_WORKER_FEELIB_STORAGE(), + NULLADDRESS, + DST_EID, + rentNanos, + false + ); +} + +cell MOCK_ADMIN_ULN_WORKER_FEELIB_INFO(int workerAddress) { + return UlnWorkerFeelibInfo::New( + workerAddress, + my_code(), + MOCK_SINGLE_LEVEL_WORKER_FEELIB_STORAGE(), + NULLADDRESS, + DST_EID, + 0, + true + ); +} + +cell MOCK_ULN_WORKER_FEELIB_INFO_WITH_FRIEND(int workerAddress, int friendAddress, int rentNanos) { + return UlnWorkerFeelibInfo::New( + workerAddress, + my_code(), + MOCK_SINGLE_LEVEL_WORKER_FEELIB_STORAGE(), + friendAddress, + DST_EID, + rentNanos, + false + ); +} + +cell MOCK_ULN_DVN_FEELIB_INFO_WITH_FRIEND(int workerAddress, int friendAddress, int rentNanos) { + cell workerStorage = DvnFeelib::New( + 1, + 1, + 1, + 1 + ); + + return UlnWorkerFeelibInfo::New( + workerAddress, + my_code(), + workerStorage, + friendAddress, + DST_EID, + rentNanos, + false + ); +} + +cell MOCK_ULN_EXECUTOR_FEELIB_INFO_WITH_FRIEND(int workerAddress, int friendAddress, int rentNanos) { + cell workerStorage = ExecutorFeelib::New( + EXECUTOR_FEELIB_LZ_RECEIVE_BASE_GAS, + EXECUTOR_FEELIB_MULTIPLIER_BPS, + EXECUTOR_FEELIB_FLOOR_MARGIN_USD, + EXECUTOR_FEELIB_NATIVE_CAP, + EXECUTOR_FEELIB_LZ_COMPOSE_BASE_GAS + ); + + return UlnWorkerFeelibInfo::New( + workerAddress, + my_code(), + workerStorage, + friendAddress, + DST_EID, + rentNanos, + false + ); +} + +cell MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE() { + return MOCK_WORKER_FEELIB_BYTECODE(53291); +} + +cell MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE_MD() { + return md::UlnWorkerFeelibBytecode::New( + MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE() + ); +} + +cell MOCK_ULN_WORKER_FEELIB_INFO_WITH_STORAGE() { + return UlnWorkerFeelibInfo::New( + getCaller(), + MOCK_DEFAULT_ULN_WORKER_FEELIB_BYTECODE(), + MOCK_SINGLE_LEVEL_WORKER_FEELIB_STORAGE(), + NULLADDRESS, + DST_EID, + RENT_NANOS, + false + ); +} + +cell MOCK_PRICE_FEED_WORKER_INFO(cell $ulnPriceFeedStorage, int workerAddress, int rentNanos) { + return UlnWorkerFeelibInfo::New( + workerAddress, + my_code(), + $ulnPriceFeedStorage, + NULLADDRESS, + DST_EID, + rentNanos, + false + ); +} + +cell MOCK_EXECUTOR_WORKER_WITH_FRIEND(int workerAddress, int friendAddress, int rentNanos) { + return UlnWorkerFeelibInfo::New( + DEFAULT_EXECUTOR, + my_code(), + MOCK_SINGLE_LEVEL_WORKER_FEELIB_STORAGE(), + DEFAULT_PRICE_FEED_ADDRESS, + DST_EID, + rentNanos, + false + ); +} + +cell MOCK_DEFAULT_PRICE_FEED_STORAGE() { + return PriceFeedFeelib::New( + DEFAULT_PRICE_RATIO, + DEFAULT_GAS_PRICE_IN_UNIT, + DEFAULT_GAS_PER_BYTE, + DEFAULT_NATIVE_PRICE_USD, + cl::nullObject(), + cl::nullObject() + ); +} + +cell MOCK_CUSTOM_PRICE_FEED_STORAGE_DEFAULT() { + return PriceFeedFeelib::New( + CUSTOM_PRICE_RATIO, + CUSTOM_GAS_PRICE_IN_UNIT, + CUSTOM_GAS_PER_BYTE, + CUSTOM_NATIVE_PRICE_USD, + cl::nullObject(), + cl::nullObject() + ); +} + +cell MOCK_DEFAULT_PRICE_FEED_STORAGE_ARB() { + return PriceFeedFeelib::New( + DEFAULT_PRICE_RATIO, + DEFAULT_GAS_PRICE_IN_UNIT, + DEFAULT_GAS_PER_BYTE, + DEFAULT_NATIVE_PRICE_USD, + ArbitrumPriceFeedExtension::New( + DEFAULT_ARB_GAS_PER_L2_TX, + DEFAULT_ARB_GAS_PER_L1_CALL_DATA_BYTE + ), + cl::nullObject() + ); +} + +cell MOCK_CUSTOM_PRICE_FEED_STORAGE_ARB() { + return PriceFeedFeelib::New( + CUSTOM_PRICE_RATIO, + CUSTOM_GAS_PRICE_IN_UNIT, + CUSTOM_GAS_PER_BYTE, + CUSTOM_NATIVE_PRICE_USD, + ArbitrumPriceFeedExtension::New( + CUSTOM_ARB_GAS_PER_L2_TX, + CUSTOM_ARB_GAS_PER_L1_CALL_DATA_BYTE + ), + cl::nullObject() + ); +} + +cell MOCK_DEFAULT_PRICE_FEED_STORAGE_OP() { + return PriceFeedFeelib::New( + DEFAULT_PRICE_RATIO, + DEFAULT_GAS_PRICE_IN_UNIT, + DEFAULT_GAS_PER_BYTE, + DEFAULT_NATIVE_PRICE_USD, + cl::nullObject(), + PriceFeedFeelib::New( + DEFAULT_OP_PRICE_RATIO, + DEFAULT_OP_GAS_PRICE_IN_UNIT, + DEFAULT_OP_GAS_PER_BYTE, + DEFAULT_OP_NATIVE_PRICE_USD, + cl::nullObject(), + cl::nullObject() + ) + ); +} + + +cell MOCK_CUSTOM_PRICE_FEED_STORAGE_OP() { + return PriceFeedFeelib::New( + CUSTOM_PRICE_RATIO, + CUSTOM_GAS_PRICE_IN_UNIT, + CUSTOM_GAS_PER_BYTE, + CUSTOM_NATIVE_PRICE_USD, + cl::nullObject(), + PriceFeedFeelib::New( + CUSTOM_OP_PRICE_RATIO, + CUSTOM_OP_GAS_PRICE_IN_UNIT, + CUSTOM_OP_GAS_PER_BYTE, + CUSTOM_OP_NATIVE_PRICE_USD, + cl::nullObject(), + cl::nullObject() + ) + ); +} + +cell MOCK_EXTRA_DATA() { + return begin_cell().store_uint256(123456).end_cell(); +} + +cell MOCK_REASON() { + return begin_cell().store_slice("bug in your code!").end_cell(); +} + +cell MOCK_DVN_LIST_WITH_REF(int numDvns) { + return _buildSendDvnCellWithMultipleRef(numDvns, CUSTOM_OPTIONAL_DVN0_ADDRESS); +} + +cell MOCK_DVN_CELL_WITH_NULL(int numDvns) { + return _buildSendDvnListCell(numDvns, NULL_DVN0_ADDRESS, false); +} + +cell MOCK_DEFAULT_REQUIRED_DVN_CELL(int numDvns, int isAdmin) { + return _buildSendDvnListCell(numDvns, DEFAULT_REQUIRED_DVN0_ADDRESS, isAdmin); +} + +cell MOCK_CUSTOM_REQUIRED_DVN_CELL(int numDvns, int isAdmin) { + return _buildSendDvnListCell(numDvns, CUSTOM_REQUIRED_DVN0_ADDRESS, isAdmin); +} + +cell MOCK_DEFAULT_OPTIONAL_DVN_CELL(int numDvns, int isAdmin) { + return _buildSendDvnListCell(numDvns, DEFAULT_OPTIONAL_DVN0_ADDRESS, isAdmin); +} + +cell MOCK_CUSTOM_OPTIONAL_DVN_CELL(int numDvns, int isAdmin) { + return _buildSendDvnListCell(numDvns, CUSTOM_OPTIONAL_DVN0_ADDRESS, isAdmin); +} + +tuple MOCK_DEFAULT_REQUIRED_DVN_LIST(int numDvns, int isAdmin) { + return _buildSendDvnListTuple(numDvns, DEFAULT_REQUIRED_DVN0_ADDRESS); +} + +tuple MOCK_SEND_DVN_LIST(int numDvns, int address) { + return _buildSendDvnListTuple(numDvns, address); +} + +cell MOCK_CUSTOM_ULN_SEND_CONFIG() { + return UlnSendConfig::New( + CUSTOM_WORKER_QUOTE_GAS_LIMIT, + CUSTOM_MAX_MESSAGE_BYTES, + false, + CUSTOM_EXECUTOR, + false, + MOCK_CUSTOM_REQUIRED_DVN_CELL(2, 0), + false, + MOCK_CUSTOM_OPTIONAL_DVN_CELL(2, 0), + false, + CUSTOM_CONFIRMATIONS + ); +} + +cell MOCK_DEFAULT_ULN_SEND_CONFIG() { + return UlnSendConfig::New( + DEFAULT_WORKER_QUOTE_GAS_LIMIT, + DEFAULT_MAX_MESSAGE_BYTES, + false, + DEFAULT_EXECUTOR, + false, + MOCK_DEFAULT_REQUIRED_DVN_CELL(2, 0), + false, + MOCK_DEFAULT_OPTIONAL_DVN_CELL(2, 0), + false, + DEFAULT_CONFIRMATIONS + ); +} + +cell MOCK_ULN_SEND() { + return md::UlnSend::New( + MOCK_LZ_SEND(), + MOCK_CUSTOM_ULN_SEND_CONFIG(), + UlnConnection::New(ULN_MANAGER_ADDRESS, MOCK_SEND_PATH(), ULN_ADDRESS), + CHANNEL_ADDRESS + ); +} + +cell MOCK_ULN_WORKER_EVENT() { + return begin_cell().store_uint256("JOB_ASSIGNED"u).end_cell(); +} + +cell MOCK_ATTESTATION(int confirmations) { + return lz::Attestation::New( + PACKET_HASH, + confirmations + ); +} + +cell MOCK_ULN_VERIFICATION_FULL(int nonce, int confirmations, int packetHash) inline_ref { + return md::UlnVerification::New( + nonce, + packetHash, + confirmations + ); +} +cell MOCK_ULN_VERIFICATION(int confirmations) { + return MOCK_ULN_VERIFICATION_FULL(NONCE, confirmations, MOCK_RECEIVE_PACKET().cl::hash()); +} + +cell MOCK_ADMIN_WORKER_LIST() { + return AddressList::serialize( + unsafeTuple([ADMIN_WORKER_ADDRESS]) + ); +} + +int MOCK_DEFAULT_REQUIRED_DVN_ADDRESS(int i) { + return DEFAULT_REQUIRED_DVN0_ADDRESS + i; +} + +int MOCK_DEFAULT_OPTIONAL_DVN_ADDRESS(int i) { + return DEFAULT_OPTIONAL_DVN0_ADDRESS + i; +} + +int MOCK_CUSTOM_REQUIRED_DVN_ADDRESS(int i) { + return CUSTOM_REQUIRED_DVN0_ADDRESS + i; +} + +int MOCK_CUSTOM_OPTIONAL_DVN_ADDRESS(int i) { + return CUSTOM_OPTIONAL_DVN0_ADDRESS + i; +} + +cell MOCK_EXECUTE_PARAMS(int targetAddress, cell calldata, int expiration, int op, int forwardingAddress) { + return md::ExecuteParams::New( + targetAddress, + calldata, + expiration, + op, + forwardingAddress + ); +} + +cell MOCK_RANDOM_EXECUTE_PARAMS() { + return MOCK_EXECUTE_PARAMS( + ARBITRARY_ADDRESS, + MOCK_EXTRA_DATA(), + now(), + OP::RANDOM, + NULLADDRESS + ); +} + +cell MOCK_COINS_AMOUNT() { + return md::CoinsAmount::New(1234567); +} + +cell MOCK_SET_ADDRESS() { + return md::SetAddress::New(ARBITRARY_ADDRESS); +} + +cell MOCK_V1_SEND_PATH() { + return lz::Path::New( + SRC_EID, + SRC_OAPP, + DST_V1_EID, + DST_OAPP + ); +} + +cell MOCK_RESOLVED_CONFIG() { + return lz::Config::New( + MOCK_SEND_PATH(), + ARBITRARY_ADDRESS, + OP::RANDOM, + MOCK_EP_CONFIG(true) + ); +} + +cell MOCK_PACKET_ID() { + return md::PacketId::New( + MOCK_RECEIVE_PATH(), + NONCE + ); +} \ No newline at end of file diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/protocolStorageTestUtils.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/protocolStorageTestUtils.fc new file mode 100644 index 00000000..bfcfda4b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/protocolStorageTestUtils.fc @@ -0,0 +1,27 @@ +#include "../src/funC++/classlib.fc"; + +#include "../src/protocol/core/baseStorage.fc"; + +() _createInitializedStorage() impure; + +() forceAuthenticate(int base_storage_idx) impure { + cell $storage = getContractStorage(); + $storage = $storage.cl::set( + base_storage_idx, + $storage.cl::get(base_storage_idx) + .cl::set(BaseStorage::initialStorage, getContractStorage()) + .cl::set(BaseStorage::authenticated, true) + ); + setContractStorage($storage); +} + +cell createContractStorage() impure; + +cell createInitializedStorage() impure { + if (get_data().begin_parse().slice_bits() < _NAME_WIDTH) { + createContractStorage(); + forceAuthenticate(BASE_STORAGE_INDEX); + _createInitializedStorage(); + } + return getContractStorage(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/testInitTxnContextMain.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/testInitTxnContextMain.fc new file mode 100644 index 00000000..a75461a9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/testInitTxnContextMain.fc @@ -0,0 +1,89 @@ +#include "../src/funC++/classlib.fc"; +#include "../src/funC++/txnContext.fc"; + +global slice base_error_msg; +;; const int do_profile = -1; ;; doesn't compile with true +const int do_profile = 0; ;; doesn't compile with false + +const int TEST_SUCCESS = -1; +const int TEST_FAILED = 0; + +const int TEST_FN_IDX = 0; +const int TEST_NAME_IDX = 1; + +;;; ===============================INTERFACE FUNCTIONS=========================== +slice _testName(); +tuple baseTest::getTests() impure; +cell baseTest::prepare(tuple testCase) impure inline; + +;;; ===============================HELPER FUNCTIONS=========================== +const int DONE = 1; +() emitDone() impure inline { + var msg = begin_cell() + .store_uint (12, 4) ;; ext_out_msg_info$11 src:MsgAddressInt () + .store_uint (1, 2) + .store_uint (256, 9) + .store_uint(DONE, 256) + .store_uint(0, 64 + 32 + 2) ;; created_lt, created_at, init:Maybe, body:Either + .end_cell(); + send_raw_message(msg, 0); +} + +() test::throwError(slice msg) impure { + ~strdump(_testName() + .str::concat(": ") + .str::concat(base_error_msg) + .str::concat(": ") + .str::concat(msg) + ); + throwError( + _testName() + .str::concat(": ") + .str::concat(base_error_msg) + .str::concat(": ") + .str::concat(msg) + ); +} + +() test::throwErrorUnless(int condition, slice msg) impure { + ifnot (condition) { + test::throwError(msg); + } +} + +slice get_test_name(int index) impure method_id { + tuple tests = baseTest::getTests(); + tuple testCase = tests.at(index); + return testCase.slice_at(TEST_NAME_IDX); +} + +;;; ===============================MAIN DRIVER FOR ALL TESTS=========================== +() main(int myBalance, int msgValue, cell inMsgFull, slice inMsgBody) impure { + ;; ignore empty messages + if (inMsgBody.slice_empty?()) { + return (); + } + + base_error_msg = "base_error_msg NOT set... "; + + tuple tests = baseTest::getTests(); + int total_tests = tests.tlen(); + + int index = 0; + while (index < total_tests) { + tuple testCase = tests.at(index); + var test_fn = testCase.at(TEST_FN_IDX); + base_error_msg = testCase.at(TEST_NAME_IDX); + + cell input = baseTest::prepare(testCase); + cell $storage = getContractStorage(); + + (int success, slice reason) = test_fn(input); + setContractStorage($storage); + test::throwErrorUnless(success == TEST_SUCCESS, reason); + + index += 1; + } + + emitDone(); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/testMain.fc b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/testMain.fc new file mode 100644 index 00000000..23394181 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/testMain.fc @@ -0,0 +1,134 @@ +#include "../src/funC++/classlib.fc"; +#include "../src/funC++/txnContext.fc"; + +global slice base_error_msg; +;; const int do_profile = -1; ;; doesn't compile with true +const int do_profile = 0; ;; doesn't compile with false + +const int TEST_SUCCESS = -1; +const int TEST_FAILED = 0; + +const int TEST_FN_IDX = 0; +const int TEST_NAME_IDX = 1; + +;;; ===============================INTERFACE FUNCTIONS=========================== +slice _testName(); +tuple baseTest::getTests() impure; +cell baseTest::prepare(tuple testCase) impure inline; + +;;; ===============================HELPER FUNCTIONS=========================== +const int DONE = 1; +() emitDone() impure inline { + var msg = begin_cell() + .store_uint (12, 4) ;; ext_out_msg_info$11 src:MsgAddressInt () + .store_uint (1, 2) + .store_uint (256, 9) + .store_uint(DONE, 256) + .store_uint(0, 64 + 32 + 2) ;; created_lt, created_at, init:Maybe, body:Either + .end_cell(); + send_raw_message(msg, 0); +} + +() test::throwError(slice msg) impure { + ~strdump(_testName() + .str::concat(": ") + .str::concat(base_error_msg) + .str::concat(": ") + .str::concat(msg) + ); + throwError( + _testName() + .str::concat(": ") + .str::concat(base_error_msg) + .str::concat(": ") + .str::concat(msg) + ); +} + +() test::throwErrorUnless(int condition, slice msg) impure { + ifnot (condition) { + test::throwError(msg); + } +} + +slice get_test_name(int index) impure method_id { + tuple tests = baseTest::getTests(); + tuple testCase = tests.at(index); + return testCase.slice_at(TEST_NAME_IDX); +} + +;;; ===============================Txn Context Manipulation=========================== + +() setCaller(int caller) impure inline { + txnContext~tset(_CALLER, caller); +} + +() setContractBalance(int balance) impure inline { + txnContext~tset(_BALANCE, balance); +} + +;;; Returns the current value of `c7`. +tuple _get_c7() impure asm "c7 PUSH"; + +;;; Updates the current value of `c7`. +() _set_c7(tuple c) impure asm "c7 POP"; + +;; *Testing function only. Do not use in production code!* +() setNewTime(int newTime) impure { + tuple c7 = _get_c7(); + _set_c7( + c7.tset(0, + c7.tuple_at(0) + .tset(3, newTime) + ) + ); +} + + +;;; ===============================MAIN DRIVER FOR ALL TESTS=========================== +() main(int myBalance, int msgValue, cell inMsgFull, slice inMsgBody) impure { + ;; ignore empty messages + if (inMsgBody.slice_empty?()) { + return (); + } + + initTxnContext( + myBalance, + msgValue, + inMsgFull, + inMsgBody + ); + + base_error_msg = "base_error_msg NOT set... "; + + int index = getOpcode(); + + ;; Allow the test framework to prank arbitrary caller + setCaller(getOrigin()); + + tuple tests = baseTest::getTests(); + if (index >= tests.tlen()) { + emitDone(); + } else { + tuple testCase = tests.at(index); + var test_fn = testCase.at(TEST_FN_IDX); + base_error_msg = testCase.at(TEST_NAME_IDX); + cell input = baseTest::prepare(testCase); + cell $storage = getContractStorage(); + (int success, slice reason) = test_fn(input); + setContractStorage($storage); + test::throwErrorUnless(success == TEST_SUCCESS, reason); + } +} + +(int, slice) test::shouldBeTrue(int condition) impure { + ifnot (condition) { + return (TEST_FAILED, "test::shouldBeTrue"); + } else { + return (TEST_SUCCESS, ""); + } +} + +(int, slice) test::shouldBeFalse(int condition) impure { + return test::shouldBeTrue(condition == false); +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/unittest.spec.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/unittest.spec.ts new file mode 100644 index 00000000..9fe0c553 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tests/unittest.spec.ts @@ -0,0 +1,231 @@ +import assert from 'assert' + +import { Cell, toNano } from '@ton/core' +import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox' + +import { UnitTest } from './UnitTest' +import { globalTestResults } from './globalTestResults' + +import '@ton/test-utils' + +function getTestSummary(testName: string, testCases: { [key: string]: boolean }): string { + const passedTests = Object.keys(testCases).filter((test) => testCases[test]) + const failedTests = Object.keys(testCases).filter((test) => !testCases[test]) + + let printStr = testName + '\n' + if (passedTests.length > 0) { + printStr += `\x1b[32m ✓ ${passedTests.length} tests passed.\n` + } + + if (failedTests.length > 0) { + printStr += `\x1b[31m ❌ ${failedTests.length} tests failed:\n` + failedTests.forEach((test) => { + printStr += ` - ${test}\n` + }) + } + return printStr +} + +export const runUnitTests = (testName: string, contractName: string): void => { + describe(testName, () => { + let code: Cell + + beforeAll(() => { + const { hex } = require(`../build/${contractName}.compiled.json`) as { hex: string } + assert(typeof hex === 'string', `Invalid artifact for ${contractName}`) + + code = Cell.fromHex(hex) + }) + + let blockchain: Blockchain + let testCase: SandboxContract + let deployer: SandboxContract + + beforeEach(async () => { + blockchain = await Blockchain.create() + + testCase = blockchain.openContract(UnitTest.createFromConfig({}, code)) + + deployer = await blockchain.treasury('deployer') + + const deployResult = await testCase.sendDeploy(deployer.getSender(), toNano('1')) + + expect(deployResult.transactions).toHaveTransaction({ + from: deployer.address, + to: testCase.address, + deploy: true, + success: true, + }) + }) + + it(testName, async () => { + let done = false + const test_results: { [key: string]: boolean } = {} + for (let testNum = 0n; !done; testNum++) { + // the check is done inside beforeEach + // blockchain and testCase are ready to use + const result = await testCase.sendTest(deployer.getSender(), toNano('10000'), testNum) + // search the json stringified result for the string "unhandled out-of-gas exception" + const txns = result.transactions + + let unhandledException = false + // starting from -1 because the first one is the external message from wallet to contract + txns.forEach((item) => { + if (item.vmLogs.toString().includes('unhandled out-of-gas exception')) { + console.log(`Test number ${testNum} OUT OF GAS`) + } + if (item.vmLogs.toString().includes('default exception handler')) { + console.error(`Unhandled exception in ${testName} test ${testNum}`) + unhandledException = true + } + }) + + done = result.externals.some((item) => { + const event_topic = item.info.dest?.value + if (event_topic == 1n) { + return true + } + return false + }) + if (!done) { + const contractTestName = await testCase.getTestName(deployer.getSender(), testNum) + let success = true + result.events.forEach((item) => { + // TODO fix this + // @ts-expect-error type mismatch + if (item.bounced as boolean) { + success = false + } + }) + if (contractTestName in test_results) { + throw new Error(`Duplicate test name ${contractTestName}`) + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + test_results[contractTestName] = success && !unhandledException + globalTestResults[testName] = test_results + expect(success) + } + } + }) + }) +} + +// Library tests +runUnitTests('Msglib Packet Codec', 'MsglibPacketCodec.test') +runUnitTests('LZ Classes', 'LzClasses.test') +runUnitTests('LZ Classes Serde', 'LzClassesSerde.test') +runUnitTests('Uln Send Config', 'UlnSendConfig.test') +runUnitTests('Uln Receive Config', 'UlnReceiveConfig.test') +runUnitTests('MsgData', 'MsgData.test') +runUnitTests('MsgData Serde', 'MsgDataSerde.test') +runUnitTests('LZ Test Utils', 'LzUtil.test') +runUnitTests('Base Contract', 'BaseContract.test') +runUnitTests('Classlib', 'Classlib.test') +runUnitTests('Pipelined Out-of-Order', 'PipelinedOutOfOrder.test') +runUnitTests('Pipelined Out-of-Order Serde', 'PipelinedOutOfOrderSerde.test') +runUnitTests('Txn Context', 'TxnContext.test') + +// Endpoint tests +runUnitTests('Controller', 'Controller.test') +runUnitTests('Actions Serde', 'ActionsSerde.test') +runUnitTests('Controller Permissions', 'Controller.permissions.test') +runUnitTests('Controller Assertions', 'Controller.assertions.test') +runUnitTests('Endpoint', 'Endpoint.test') +runUnitTests('Endpoint Permissions', 'Endpoint.permissions.test') +runUnitTests('Endpoint SetEpConfigDefaults', 'EndpointSetEpConfigDefaults.test') +runUnitTests('Endpoint Serde', 'EndpointSerde.test') +runUnitTests('Channel Send', 'ChannelSend.test') +runUnitTests('Channel Serde', 'ChannelSerde.test') +runUnitTests('Channel Msglib Send Callback', 'ChannelMsglibSendCallback.test') +runUnitTests('Channel Receive', 'ChannelReceive.test') +runUnitTests('Channel Receive Lz Receive Callback', 'ChannelReceiveCallback.test') +runUnitTests('Channel CommitPacket', 'ChannelCommitPacket.test') +runUnitTests('Channel Receive View', 'ChannelReceiveView.test') +runUnitTests('Channel Burn', 'ChannelBurn.test') +runUnitTests('Channel Burn Default Config', 'ChannelBurnDefaultConfig.test') +runUnitTests('Channel Initialize', 'ChannelInitialize.test') +runUnitTests('Channel Nilify', 'ChannelNilify.test') +runUnitTests('Channel Nilify Default Config', 'ChannelNilifyDefaultConfig.test') +runUnitTests('Channel Msglib Integration', 'ChannelMsglibIntegration.test') +runUnitTests('Channel Config', 'ChannelConfig.test') +runUnitTests('Channel Permissions', 'Channel.permissions.test') + +// // OApp tests +// // runUnitTests('Counter', 'Counter.test') +// // runUnitTests('Counter Permissions', 'Counter.permissions.test') +// // runUnitTests('Counter Setters', 'Counter.setters.test') + +// Msglib Tests +// Simple Msglib tests +// runUnitTests('SML Manager', 'SmlManager.test') +// runUnitTests('SML Manager Permissions', 'SmlManager.permissions.test') +// runUnitTests('SML Connection', 'SmlConnection.test') +// runUnitTests('SML Connection Permissions', 'SmlConnection.permissions.test') + +// UltralightNode tests +runUnitTests('ULN MsgData Serde', 'UlnMsgDataSerde.test') +runUnitTests('ULN Manager', 'UlnManager.test') +runUnitTests('ULN Manager Permissions', 'UlnManagerPermissions.test') +runUnitTests('ULN Manager Util', 'UlnManagerUtil.test') +runUnitTests('ULN', 'Uln.test') +runUnitTests('ULN Permissions', 'UlnPermissions.test') +runUnitTests('ULN Management', 'UlnManagement.test') +runUnitTests('ULN Util', 'UlnUtil.test') +runUnitTests('ULN Serde', 'UlnSerde.test') +runUnitTests('ULN Send With Mock Fee Lib', 'UlnSend.test') +runUnitTests('ULN Send With Default Executor Fee Lib', 'UlnSendWithDefaultExecFeeLib.test') +runUnitTests('ULN Send With Arbitrum Executor Fee Lib', 'UlnSendWithArbExecFeeLib.test') +runUnitTests('ULN Send With Optimism Executor Fee Lib', 'UlnSendWithOpExecFeeLib.test') +runUnitTests('ULN Send With Arbitrum Dvn Fee Lib', 'UlnSendWithArbDvnFeeLib.test') +runUnitTests('ULN Send With Optimism Dvn Fee Lib', 'UlnSendWithOpDvnFeeLib.test') +runUnitTests('ULN Send With Default Dvn Fee Lib', 'UlnSendWithDefaultDvnFeeLib.test') +runUnitTests('ULN Send With Malicious Fee Lib 1', 'badFeeLib1.test') +runUnitTests('ULN Send With Malicious Fee Lib 2', 'badFeeLib2.test') +runUnitTests('ULN Send With Malicious Fee Lib 3', 'badFeeLib3.test') +runUnitTests('ULN Send With Malicious Fee Lib 4', 'badFeeLib4.test') +runUnitTests('ULN Send With Malicious Fee Lib 5', 'badFeeLib5.test') +runUnitTests('ULN Send With Malicious Fee Lib 6', 'badFeeLib6.test') +runUnitTests('ULN Send With Malicious Fee Lib 7', 'badFeeLib7.test') +runUnitTests('ULN Send With Malicious Fee Lib 8', 'badFeeLib8.test') +runUnitTests('ULN Send With Malicious Fee Lib 9', 'badFeeLib9.test') +runUnitTests('ULN Send With Malicious Fee Lib 10', 'badFeeLib10.test') +runUnitTests('ULN Send With Malicious Fee Lib 11', 'badFeeLib11.test') +runUnitTests('ULN Send With Malicious Fee Lib 12', 'badFeeLib12.test') +runUnitTests('ULN Connection', 'UlnConnection.test') +runUnitTests('ULN Connection Serde', 'UlnConnectionSerde.test') +runUnitTests('ULN Connection Permissions', 'UlnConnectionPermissions.test') +runUnitTests('ULN Send Worker Factory', 'UlnSendWorkerFactory.test') + +// priceFeed Cache tests +runUnitTests('priceFeed Cache', 'PriceFeedCache.test') +runUnitTests('priceFeed Cache serde', 'PriceFeedCacheSerde.test') +runUnitTests('priceFeed Cache Permissions', 'PriceFeedCache.test.permissions') + +// Proxy tests +runUnitTests('Proxy', 'Proxy.test') +runUnitTests('Proxy Permissions', 'Proxy.permissions.test') + +// Worker tests +runUnitTests('Worker Core', 'WorkerCore.test') +runUnitTests('Worker Core Serde', 'WorkerCoreSerde.test') +runUnitTests('Worker Core MsgData Serde', 'WorkerCoreMsgDataSerde.test') + +runUnitTests('Executor', 'Executor.test') +runUnitTests('Executor Permissions', 'ExecutorPermissions.test') +runUnitTests('Executor Serde', 'ExecutorSerde.test') +runUnitTests('Dvn', 'Dvn.test') +runUnitTests('Dvn Permissions', 'DvnPermissions.test') +runUnitTests('Dvn Serde', 'DvnSerde.test') + +runUnitTests('PriceFeedFeeLib serde', 'PriceFeedFeeLibSerde.test') +runUnitTests('ExecutorFeeLib serde', 'ExecutorFeeLibSerde.test') +runUnitTests('DvnFeeLib serde', 'DvnFeeLibSerde.test') + +// todo: make this look nicer +afterAll(() => { + let printString = '' + for (const testName in globalTestResults) { + printString += '\x1b[0m' + getTestSummary(testName, globalTestResults[testName]) + } + console.log(printString) +}) diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/tsconfig.json b/lib/LayerZero-v2/packages/layerzero-v2/ton/tsconfig.json new file mode 100644 index 00000000..9ca3cc90 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2020", + "outDir": "dist", + "module": "commonjs", + "declaration": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ActionsSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ActionsSerde.test.compile.ts new file mode 100644 index 00000000..45ef130d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ActionsSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/funC++/tests/actions/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/AllStorages.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/AllStorages.compile.ts new file mode 100644 index 00000000..3ec23458 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/AllStorages.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/apps/allStorages/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/AllStorages.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/AllStorages.test.compile.ts new file mode 100644 index 00000000..57d8e716 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/AllStorages.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/apps/allStorages/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/BaseContract.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/BaseContract.test.compile.ts new file mode 100644 index 00000000..73bd71bc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/BaseContract.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/funC++/tests/baseContract/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Channel.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Channel.compile.ts new file mode 100644 index 00000000..ef1d4602 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Channel.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Channel.permissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Channel.permissions.test.compile.ts new file mode 100644 index 00000000..94ae529d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Channel.permissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelBurn.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelBurn.test.compile.ts new file mode 100644 index 00000000..2db5e7d6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelBurn.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelBurn.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelBurnDefaultConfig.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelBurnDefaultConfig.test.compile.ts new file mode 100644 index 00000000..c9378ea8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelBurnDefaultConfig.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelBurnDefaultConfig.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelCommitPacket.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelCommitPacket.test.compile.ts new file mode 100644 index 00000000..a9d2d91b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelCommitPacket.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelCommitPacket.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelConfig.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelConfig.test.compile.ts new file mode 100644 index 00000000..25e79866 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelConfig.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelConfig.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelInitialize.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelInitialize.test.compile.ts new file mode 100644 index 00000000..8c105a7b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelInitialize.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelInitialize.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelMsglibIntegration.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelMsglibIntegration.test.compile.ts new file mode 100644 index 00000000..177d7800 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelMsglibIntegration.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelMsglibIntegration.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelMsglibSendCallback.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelMsglibSendCallback.test.compile.ts new file mode 100644 index 00000000..ee815626 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelMsglibSendCallback.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelMsglibSendCallback.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelNilify.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelNilify.test.compile.ts new file mode 100644 index 00000000..b13f0cc1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelNilify.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelNilify.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelNilifyDefaultConfig.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelNilifyDefaultConfig.test.compile.ts new file mode 100644 index 00000000..59719cdc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelNilifyDefaultConfig.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelNilifyDefaultConfig.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceive.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceive.test.compile.ts new file mode 100644 index 00000000..8533be6b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceive.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelReceive.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceiveCallback.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceiveCallback.test.compile.ts new file mode 100644 index 00000000..0197f935 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceiveCallback.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelReceiveCallback.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceiveView.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceiveView.test.compile.ts new file mode 100644 index 00000000..893b38ea --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelReceiveView.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelReceiveView.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelSend.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelSend.test.compile.ts new file mode 100644 index 00000000..a398a2f8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelSend.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelSend.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelSerde.test.compile.ts new file mode 100644 index 00000000..45cdc02c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ChannelSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/channel/tests/channelSerde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Classlib.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Classlib.test.compile.ts new file mode 100644 index 00000000..85206fa1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Classlib.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/funC++/tests/classlib/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ComputeDataSizeGas.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ComputeDataSizeGas.test.compile.ts new file mode 100644 index 00000000..58435340 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ComputeDataSizeGas.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/computeSizeGasTest/computeSizeGasTest.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Connection.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Connection.compile.ts new file mode 100644 index 00000000..253559d3 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Connection.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/ulnConnection/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.assertions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.assertions.test.compile.ts new file mode 100644 index 00000000..98c7745d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.assertions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/controller/tests/assertions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.compile.ts new file mode 100644 index 00000000..3d9e699c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/controller/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.permissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.permissions.test.compile.ts new file mode 100644 index 00000000..0a5a1def --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.permissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/controller/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.test.compile.ts new file mode 100644 index 00000000..f1d65db1 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Controller.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/controller/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.compile.ts new file mode 100644 index 00000000..0c266ade --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/apps/counter/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.permissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.permissions.test.compile.ts new file mode 100644 index 00000000..d2f19e8f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.permissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/apps/counter/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.setters.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.setters.test.compile.ts new file mode 100644 index 00000000..9ac8d6da --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.setters.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/apps/counter/tests/setters.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.test.compile.ts new file mode 100644 index 00000000..f4a3e7f4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Counter.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/apps/counter/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Dvn.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Dvn.compile.ts new file mode 100644 index 00000000..d67146f2 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Dvn.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/dvn/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Dvn.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Dvn.test.compile.ts new file mode 100644 index 00000000..faeb257d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Dvn.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/dvn/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnFeeLib.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnFeeLib.compile.ts new file mode 100644 index 00000000..08de5b50 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnFeeLib.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnFeeLibSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnFeeLibSerde.test.compile.ts new file mode 100644 index 00000000..bfefa5ff --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnFeeLibSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/dvnFeeLib/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnPermissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnPermissions.test.compile.ts new file mode 100644 index 00000000..d6df82e2 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnPermissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/dvn/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnSerde.test.compile.ts new file mode 100644 index 00000000..3313f3eb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/DvnSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/dvn/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.compile.ts new file mode 100644 index 00000000..34991254 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/endpoint/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.permissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.permissions.test.compile.ts new file mode 100644 index 00000000..f201d0aa --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.permissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/endpoint/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.test.compile.ts new file mode 100644 index 00000000..23a1eb1b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Endpoint.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/endpoint/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/EndpointSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/EndpointSerde.test.compile.ts new file mode 100644 index 00000000..e17decab --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/EndpointSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/endpoint/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/EndpointSetEpConfigDefaults.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/EndpointSetEpConfigDefaults.test.compile.ts new file mode 100644 index 00000000..d749e46b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/EndpointSetEpConfigDefaults.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/endpoint/tests/endpointSetEpConfigDefaults.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Executor.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Executor.compile.ts new file mode 100644 index 00000000..a8ee7ab0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Executor.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/executor/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Executor.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Executor.test.compile.ts new file mode 100644 index 00000000..07b9678d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Executor.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/executor/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorFeeLib.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorFeeLib.compile.ts new file mode 100644 index 00000000..f40d0abc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorFeeLib.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorFeeLibSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorFeeLibSerde.test.compile.ts new file mode 100644 index 00000000..6d372d6f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorFeeLibSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/executorFeeLib/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorPermissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorPermissions.test.compile.ts new file mode 100644 index 00000000..7412c43a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorPermissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/executor/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorSerde.test.compile.ts new file mode 100644 index 00000000..9acec319 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ExecutorSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/executor/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzClasses.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzClasses.test.compile.ts new file mode 100644 index 00000000..927de278 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzClasses.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/classes/lz/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzClassesSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzClassesSerde.test.compile.ts new file mode 100644 index 00000000..e0f452dc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzClassesSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/classes/lz/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzUtil.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzUtil.test.compile.ts new file mode 100644 index 00000000..d7bd04ab --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/LzUtil.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/funC++/tests/util/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MaliciousFeeLib.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MaliciousFeeLib.compile.ts new file mode 100644 index 00000000..1db2cd0a --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MaliciousFeeLib.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/maliciousFeeLib/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsgData.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsgData.test.compile.ts new file mode 100644 index 00000000..b46e73d3 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsgData.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/classes/msgdata/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsgDataSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsgDataSerde.test.compile.ts new file mode 100644 index 00000000..dcc79e05 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsgDataSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/classes/msgdata/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsglibPacketCodec.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsglibPacketCodec.test.compile.ts new file mode 100644 index 00000000..169b2e91 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MsglibPacketCodec.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/tests/msglibPacketCodec.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MultiSig.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MultiSig.compile.ts new file mode 100644 index 00000000..96501b23 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MultiSig.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/multisig/multisig.func'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MultiSigOrder.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MultiSigOrder.compile.ts new file mode 100644 index 00000000..13f4371c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/MultiSigOrder.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/multisig/order.func'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PipelinedOutOfOrder.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PipelinedOutOfOrder.test.compile.ts new file mode 100644 index 00000000..578b0d48 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PipelinedOutOfOrder.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/funC++/tests/POOO/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PipelinedOutOfOrderSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PipelinedOutOfOrderSerde.test.compile.ts new file mode 100644 index 00000000..d8dd1fff --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PipelinedOutOfOrderSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/funC++/tests/POOO/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.compile.ts new file mode 100644 index 00000000..27c972db --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/priceFeedCache/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.test.compile.ts new file mode 100644 index 00000000..4587f76b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/priceFeedCache/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.test.permissions.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.test.permissions.compile.ts new file mode 100644 index 00000000..13a40d3d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCache.test.permissions.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/priceFeedCache/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCacheSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCacheSerde.test.compile.ts new file mode 100644 index 00000000..8e4b5607 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedCacheSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/priceFeedCache/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibArbitrum.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibArbitrum.compile.ts new file mode 100644 index 00000000..edc87e5f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibArbitrum.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibArbitrum.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibArbitrum.test.compile.ts new file mode 100644 index 00000000..0013f428 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibArbitrum.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/arbitrum/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibDefault.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibDefault.compile.ts new file mode 100644 index 00000000..41e2ac1f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibDefault.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibDefault.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibDefault.test.compile.ts new file mode 100644 index 00000000..f077fcfc --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibDefault.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/default/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibOptimism.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibOptimism.compile.ts new file mode 100644 index 00000000..48ee1191 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibOptimism.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibOptimism.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibOptimism.test.compile.ts new file mode 100644 index 00000000..d2eed7e9 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibOptimism.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/optimism/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibSerde.test.compile.ts new file mode 100644 index 00000000..238e8af0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/PriceFeedFeeLibSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/priceFeedFeeLib/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.compile.ts new file mode 100644 index 00000000..370eb356 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/proxy/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.permissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.permissions.test.compile.ts new file mode 100644 index 00000000..fc130116 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.permissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/proxy/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.test.compile.ts new file mode 100644 index 00000000..0fc3b1c4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Proxy.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/proxy/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.compile.ts new file mode 100644 index 00000000..c0123dd4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/simpleMsglib/smlConnection/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.permissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.permissions.test.compile.ts new file mode 100644 index 00000000..9b872f8b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.permissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/simpleMsglib/smlConnection/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.test.compile.ts new file mode 100644 index 00000000..d6edef6f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlConnection.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/simpleMsglib/smlConnection/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.compile.ts new file mode 100644 index 00000000..e80ea257 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/simpleMsglib/smlManager/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.permissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.permissions.test.compile.ts new file mode 100644 index 00000000..63868b50 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.permissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/simpleMsglib/smlManager/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.test.compile.ts new file mode 100644 index 00000000..5e723564 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/SmlManager.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/simpleMsglib/smlManager/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/TxnContext.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/TxnContext.test.compile.ts new file mode 100644 index 00000000..25f21f09 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/TxnContext.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/funC++/tests/txnContext/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Uln.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Uln.compile.ts new file mode 100644 index 00000000..7cf3e553 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Uln.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Uln.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Uln.test.compile.ts new file mode 100644 index 00000000..28515b4d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/Uln.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnection.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnection.compile.ts new file mode 100644 index 00000000..253559d3 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnection.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/ulnConnection/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnection.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnection.test.compile.ts new file mode 100644 index 00000000..b3c44c97 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnection.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/ulnConnection/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnectionPermissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnectionPermissions.test.compile.ts new file mode 100644 index 00000000..b33030ca --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnectionPermissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/ulnConnection/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnectionSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnectionSerde.test.compile.ts new file mode 100644 index 00000000..25133e83 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnConnectionSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/ulnConnection/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagement.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagement.test.compile.ts new file mode 100644 index 00000000..8782c25d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagement.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/management.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManager.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManager.compile.ts new file mode 100644 index 00000000..76eba6d0 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManager.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/ulnManager/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManager.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManager.test.compile.ts new file mode 100644 index 00000000..12fef59f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManager.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/ulnManager/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagerPermissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagerPermissions.test.compile.ts new file mode 100644 index 00000000..408d4c34 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagerPermissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/ulnManager/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagerUtil.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagerUtil.test.compile.ts new file mode 100644 index 00000000..5da2935d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnManagerUtil.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/ulnManager/tests/util.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnMsgDataSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnMsgDataSerde.test.compile.ts new file mode 100644 index 00000000..44f7b5a8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnMsgDataSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/msgdata/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnPermissions.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnPermissions.test.compile.ts new file mode 100644 index 00000000..adbe4cb3 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnPermissions.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/permissions.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnReceiveConfig.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnReceiveConfig.test.compile.ts new file mode 100644 index 00000000..dac482ad --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnReceiveConfig.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/classes/lz/tests/UlnReceiveConfig.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSend.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSend.test.compile.ts new file mode 100644 index 00000000..9c7a7135 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSend.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/ulnSend.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendConfig.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendConfig.test.compile.ts new file mode 100644 index 00000000..0db9125b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendConfig.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/classes/lz/tests/UlnSendConfig.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithArbDvnFeeLib.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithArbDvnFeeLib.test.compile.ts new file mode 100644 index 00000000..28542664 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithArbDvnFeeLib.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbDvnFeeLib.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithArbExecFeeLib.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithArbExecFeeLib.test.compile.ts new file mode 100644 index 00000000..7c1ffe8d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithArbExecFeeLib.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithArbExecFeeLib.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithDefaultDvnFeeLib.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithDefaultDvnFeeLib.test.compile.ts new file mode 100644 index 00000000..d4137b50 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithDefaultDvnFeeLib.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultDvnFeeLib.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithDefaultExecFeeLib.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithDefaultExecFeeLib.test.compile.ts new file mode 100644 index 00000000..736a8313 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithDefaultExecFeeLib.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithDefaultExecFeeLib.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithOpDvnFeeLib.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithOpDvnFeeLib.test.compile.ts new file mode 100644 index 00000000..9658fcfa --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithOpDvnFeeLib.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpDvnFeeLib.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithOpExecFeeLib.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithOpExecFeeLib.test.compile.ts new file mode 100644 index 00000000..45971a50 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWithOpExecFeeLib.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/ulnSendWithOpExecFeeLib.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWorkerFactory.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWorkerFactory.test.compile.ts new file mode 100644 index 00000000..1c0fc3d7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSendWorkerFactory.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/workerFeeLibs/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSerde.test.compile.ts new file mode 100644 index 00000000..cd2e65bb --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnUtil.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnUtil.test.compile.ts new file mode 100644 index 00000000..ae7cb0a4 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/UlnUtil.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/util.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCore.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCore.test.compile.ts new file mode 100644 index 00000000..ab5a68bf --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCore.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/core/tests/main.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCoreMsgDataSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCoreMsgDataSerde.test.compile.ts new file mode 100644 index 00000000..249f2ce8 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCoreMsgDataSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/msgdata/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCoreSerde.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCoreSerde.test.compile.ts new file mode 100644 index 00000000..d441bc47 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/WorkerCoreSerde.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/workers/core/tests/serde.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ZroMinter.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ZroMinter.compile.ts new file mode 100644 index 00000000..2e55ff86 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ZroMinter.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/jettons/zro/minter.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ZroWallet.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ZroWallet.compile.ts new file mode 100644 index 00000000..9b310d9d --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/ZroWallet.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/jettons/zro/wallet.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib1.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib1.test.compile.ts new file mode 100644 index 00000000..06910580 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib1.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib1.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib10.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib10.test.compile.ts new file mode 100644 index 00000000..a3a3ffb2 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib10.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib10.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib11.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib11.test.compile.ts new file mode 100644 index 00000000..39b648b7 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib11.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib11.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib12.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib12.test.compile.ts new file mode 100644 index 00000000..4e6f471e --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib12.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib12.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib2.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib2.test.compile.ts new file mode 100644 index 00000000..efb36988 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib2.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib2.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib3.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib3.test.compile.ts new file mode 100644 index 00000000..37f7624c --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib3.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib3.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib4.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib4.test.compile.ts new file mode 100644 index 00000000..4d4e6270 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib4.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib4.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib5.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib5.test.compile.ts new file mode 100644 index 00000000..732b33d6 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib5.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib5.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib6.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib6.test.compile.ts new file mode 100644 index 00000000..436fed9b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib6.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib6.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib7.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib7.test.compile.ts new file mode 100644 index 00000000..e894fc91 --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib7.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib7.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib8.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib8.test.compile.ts new file mode 100644 index 00000000..8471ad3b --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib8.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib8.fc'], +} diff --git a/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib9.test.compile.ts b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib9.test.compile.ts new file mode 100644 index 00000000..b899c20f --- /dev/null +++ b/lib/LayerZero-v2/packages/layerzero-v2/ton/wrappers/badFeeLib9.test.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint' + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['src/protocol/msglibs/ultralightnode/uln/tests/badFeeLib/badFeeLib9.fc'], +} diff --git a/lib/devtools/.changeset/pink-chairs-double.md b/lib/devtools/.changeset/pink-chairs-double.md new file mode 100644 index 00000000..0e27e7bd --- /dev/null +++ b/lib/devtools/.changeset/pink-chairs-double.md @@ -0,0 +1,5 @@ +--- +"create-lz-oapp": minor +--- + +disable Yarn diff --git a/lib/devtools/.github/USAGE.md b/lib/devtools/.github/USAGE.md new file mode 100644 index 00000000..64ee334f --- /dev/null +++ b/lib/devtools/.github/USAGE.md @@ -0,0 +1,23 @@ +# GitHub Actions + +Since we operate with multiple architectures, we need to build and publish the images for each architecture. +We also need to maintain architecture specific caches of the images to prevent the workflow from using the cache of the wrong architecture - notably affects hardhat cache. + +We use the `reusable-publish-docker.yaml` workflow to build and publish the images for each architecture. + +We use the `reusable-test.yaml` workflow to test the images for each architecture. + +Under each workflow, we have a matrix that includes the architecture and the runner. + +Runners as of the last change (2025-02-11): + +- ubuntu-latest-16xlarge for amd64 +- ubuntu-latest-16xlarge-arm for arm64 + +This generates an entire workflow run for each architecture. + +This also means we need to select the right image for the user's architecture. + +We can do this by using the `matrix` keyword in the workflow. + +Original image: \ No newline at end of file diff --git a/lib/devtools/examples/oapp-aptos-move/CHANGELOG.md b/lib/devtools/examples/oapp-aptos-move/CHANGELOG.md new file mode 100644 index 00000000..6ab521a6 --- /dev/null +++ b/lib/devtools/examples/oapp-aptos-move/CHANGELOG.md @@ -0,0 +1,13 @@ +# @layerzerolabs/oapp-aptos-example + +## 0.1.1 + +### Patch Changes + +- e256387: Updating packages + +## 0.1.0 + +### Minor Changes + +- 7609b9b: Add optimizer in foundry.toml diff --git a/lib/devtools/examples/oft-adapter-aptos-move/CHANGELOG.md b/lib/devtools/examples/oft-adapter-aptos-move/CHANGELOG.md new file mode 100644 index 00000000..4abc5c7d --- /dev/null +++ b/lib/devtools/examples/oft-adapter-aptos-move/CHANGELOG.md @@ -0,0 +1,7 @@ +# @layerzerolabs/oft-adapter-aptos-move-example + +## 0.0.2 + +### Patch Changes + +- e256387: Updating packages diff --git a/lib/devtools/examples/oft-aptos-move/CHANGELOG.md b/lib/devtools/examples/oft-aptos-move/CHANGELOG.md new file mode 100644 index 00000000..352ccbc9 --- /dev/null +++ b/lib/devtools/examples/oft-aptos-move/CHANGELOG.md @@ -0,0 +1,7 @@ +# @layerzerolabs/oft-aptos-move-example + +## 0.0.2 + +### Patch Changes + +- e256387: Updating packages diff --git a/lib/devtools/packages/devtools-extensible-cli/CHANGELOG.md b/lib/devtools/packages/devtools-extensible-cli/CHANGELOG.md new file mode 100644 index 00000000..134bb127 --- /dev/null +++ b/lib/devtools/packages/devtools-extensible-cli/CHANGELOG.md @@ -0,0 +1,13 @@ +# @layerzerolabs/devtools-extensible-cli + +## 0.0.3 + +### Patch Changes + +- e256387: Updating packages + +## 0.0.2 + +### Patch Changes + +- 8cc2d40: Explicitly publish move packages diff --git a/lib/devtools/packages/devtools-move/CHANGELOG.md b/lib/devtools/packages/devtools-move/CHANGELOG.md new file mode 100644 index 00000000..c8617f6b --- /dev/null +++ b/lib/devtools/packages/devtools-move/CHANGELOG.md @@ -0,0 +1,13 @@ +# @layerzerolabs/devtools-move + +## 0.0.3 + +### Patch Changes + +- e256387: Updating packages + +## 0.0.2 + +### Patch Changes + +- 8cc2d40: Explicitly publish move packages diff --git a/lib/devtools/packages/oapp-alt-evm/CHANGELOG.md b/lib/devtools/packages/oapp-alt-evm/CHANGELOG.md new file mode 100644 index 00000000..69660c49 --- /dev/null +++ b/lib/devtools/packages/oapp-alt-evm/CHANGELOG.md @@ -0,0 +1,7 @@ +# @layerzerolabs/oapp-alt-evm + +## 0.0.2 + +### Patch Changes + +- e256387: Updating packages diff --git a/lib/devtools/packages/oft-alt-evm/CHANGELOG.md b/lib/devtools/packages/oft-alt-evm/CHANGELOG.md new file mode 100644 index 00000000..2b102898 --- /dev/null +++ b/lib/devtools/packages/oft-alt-evm/CHANGELOG.md @@ -0,0 +1,7 @@ +# @layerzerolabs/oft-alt-evm + +## 0.0.2 + +### Patch Changes + +- e256387: Updating packages diff --git a/lib/devtools/packages/oft-move/CHANGELOG.md b/lib/devtools/packages/oft-move/CHANGELOG.md new file mode 100644 index 00000000..7171735d --- /dev/null +++ b/lib/devtools/packages/oft-move/CHANGELOG.md @@ -0,0 +1,13 @@ +# @layerzerolabs/oft-move + +## 0.0.3 + +### Patch Changes + +- e256387: Updating packages + +## 0.0.2 + +### Patch Changes + +- 8cc2d40: Explicitly publish move packages diff --git a/lib/devtools/packages/test-devtools-ton/CHANGELOG.md b/lib/devtools/packages/test-devtools-ton/CHANGELOG.md new file mode 100644 index 00000000..6da61dd4 --- /dev/null +++ b/lib/devtools/packages/test-devtools-ton/CHANGELOG.md @@ -0,0 +1,7 @@ +# @layerzerolabs/test-devtools-ton + +## 0.0.2 + +### Patch Changes + +- e256387: Updating packages diff --git a/lib/forge-std/CONTRIBUTING.md b/lib/forge-std/CONTRIBUTING.md new file mode 100644 index 00000000..89b75f3f --- /dev/null +++ b/lib/forge-std/CONTRIBUTING.md @@ -0,0 +1,193 @@ +## Contributing to Foundry + +Thanks for your interest in improving Foundry! + +There are multiple opportunities to contribute at any level. It doesn't matter if you are just getting started with Rust or are the most weathered expert, we can use your help. + +This document will help you get started. **Do not let the document intimidate you**. +It should be considered as a guide to help you navigate the process. + +The [dev Telegram][dev-tg] is available for any concerns you may have that are not covered in this guide. + +### Code of Conduct + +The Foundry project adheres to the [Rust Code of Conduct][rust-coc]. This code of conduct describes the _minimum_ behavior expected from all contributors. + +Instances of violations of the Code of Conduct can be reported by contacting the team at [me@gakonst.com](mailto:me@gakonst.com). + +### Ways to contribute + +There are fundamentally four ways an individual can contribute: + +1. **By opening an issue:** For example, if you believe that you have uncovered a bug + in Foundry, creating a new issue in the issue tracker is the way to report it. +2. **By adding context:** Providing additional context to existing issues, + such as screenshots and code snippets, which help resolve issues. +3. **By resolving issues:** Typically this is done in the form of either + demonstrating that the issue reported is not a problem after all, or more often, + by opening a pull request that fixes the underlying problem, in a concrete and + reviewable manner. + +**Anybody can participate in any stage of contribution**. We urge you to participate in the discussion +around bugs and participate in reviewing PRs. + +### Contributions Related to Spelling and Grammar + +At this time, we will not be accepting contributions that only fix spelling or grammatical errors in documentation, code or +elsewhere. + +### Asking for help + +If you have reviewed existing documentation and still have questions, or you are having problems, you can get help in the following ways: + +- **Asking in the support Telegram:** The [Foundry Support Telegram][support-tg] is a fast and easy way to ask questions. +- **Opening a discussion:** This repository comes with a discussions board where you can also ask for help. Click the "Discussions" tab at the top. + +As Foundry is still in heavy development, the documentation can be a bit scattered. +The [Foundry Book][foundry-book] is our current best-effort attempt at keeping up-to-date information. + +### Submitting a bug report + +When filing a new bug report in the issue tracker, you will be presented with a basic form to fill out. + +If you believe that you have uncovered a bug, please fill out the form to the best of your ability. Do not worry if you cannot answer every detail; just fill in what you can. Contributors will ask follow-up questions if something is unclear. + +The most important pieces of information we need in a bug report are: + +- The Foundry version you are on (and that it is up to date) +- The platform you are on (Windows, macOS, an M1 Mac or Linux) +- Code snippets if this is happening in relation to testing or building code +- Concrete steps to reproduce the bug + +In order to rule out the possibility of the bug being in your project, the code snippets should be as minimal +as possible. It is better if you can reproduce the bug with a small snippet as opposed to an entire project! + +See [this guide][mcve] on how to create a minimal, complete, and verifiable example. + +### Submitting a feature request + +When adding a feature request in the issue tracker, you will be presented with a basic form to fill out. + +Please include as detailed of an explanation as possible of the feature you would like, adding additional context if necessary. + +If you have examples of other tools that have the feature you are requesting, please include them as well. + +### Resolving an issue + +Pull requests are the way concrete changes are made to the code, documentation, and dependencies of Foundry. + +Even minor pull requests, such as those fixing wording, are greatly appreciated. Before making a large change, it is usually +a good idea to first open an issue describing the change to solicit feedback and guidance. This will increase +the likelihood of the PR getting merged. + +Please make sure that the following commands pass if you have changed the code: + +```sh +forge fmt --check +forge test -vvv +``` + +To make sure your changes are compatible with all compiler version targets, run the following commands: + +```sh +forge build --skip test --use solc:0.6.2 +forge build --skip test --use solc:0.6.12 +forge build --skip test --use solc:0.7.0 +forge build --skip test --use solc:0.7.6 +forge build --skip test --use solc:0.8.0 +``` + +The CI will also ensure that the code is formatted correctly and that the tests are passing across all compiler version targets. + +#### Adding cheatcodes + +Please follow the guide outlined in the [cheatcodes](https://github.com/foundry-rs/foundry/blob/master/docs/dev/cheatcodes.md#adding-a-new-cheatcode) documentation of Foundry. + +When making modifications to the native cheatcodes or adding new ones, please make sure to run [`./scripts/vm.py`](./scripts/vm.py) to update the cheatcodes in the [`src/Vm.sol`](./src/Vm.sol) file. + +By default the script will automatically generate the cheatcodes from the [`cheatcodes.json`](https://raw.githubusercontent.com/foundry-rs/foundry/master/crates/cheatcodes/assets/cheatcodes.json) file but alternatively you can provide a path to a JSON file containing the Vm interface, as generated by Foundry, with the `--from` flag. + +```sh +./scripts/vm.py --from path/to/cheatcodes.json +``` + +It is possible that the resulting [`src/Vm.sol`](./src/Vm.sol) file will have some changes that are not directly related to your changes, this is not a problem. + +#### Commits + +It is a recommended best practice to keep your changes as logically grouped as possible within individual commits. There is no limit to the number of commits any single pull request may have, and many contributors find it easier to review changes that are split across multiple commits. + +That said, if you have a number of commits that are "checkpoints" and don't represent a single logical change, please squash those together. + +#### Opening the pull request + +From within GitHub, opening a new pull request will present you with a template that should be filled out. Please try your best at filling out the details, but feel free to skip parts if you're not sure what to put. + +#### Discuss and update + +You will probably get feedback or requests for changes to your pull request. +This is a big part of the submission process, so don't be discouraged! Some contributors may sign off on the pull request right away, others may have more detailed comments or feedback. +This is a necessary part of the process in order to evaluate whether the changes are correct and necessary. + +**Any community member can review a PR, so you might get conflicting feedback**. +Keep an eye out for comments from code owners to provide guidance on conflicting feedback. + +#### Reviewing pull requests + +**Any Foundry community member is welcome to review any pull request**. + +All contributors who choose to review and provide feedback on pull requests have a responsibility to both the project and individual making the contribution. Reviews and feedback must be helpful, insightful, and geared towards improving the contribution as opposed to simply blocking it. If there are reasons why you feel the PR should not be merged, explain what those are. Do not expect to be able to block a PR from advancing simply because you say "no" without giving an explanation. Be open to having your mind changed. Be open to working _with_ the contributor to make the pull request better. + +Reviews that are dismissive or disrespectful of the contributor or any other reviewers are strictly counter to the Code of Conduct. + +When reviewing a pull request, the primary goals are for the codebase to improve and for the person submitting the request to succeed. **Even if a pull request is not merged, the submitter should come away from the experience feeling like their effort was not unappreciated**. Every PR from a new contributor is an opportunity to grow the community. + +##### Review a bit at a time + +Do not overwhelm new contributors. + +It is tempting to micro-optimize and make everything about relative performance, perfect grammar, or exact style matches. Do not succumb to that temptation.. + +Focus first on the most significant aspects of the change: + +1. Does this change make sense for Foundry? +2. Does this change make Foundry better, even if only incrementally? +3. Are there clear bugs or larger scale issues that need attending? +4. Are the commit messages readable and correct? If it contains a breaking change, is it clear enough? + +Note that only **incremental** improvement is needed to land a PR. This means that the PR does not need to be perfect, only better than the status quo. Follow-up PRs may be opened to continue iterating. + +When changes are necessary, _request_ them, do not _demand_ them, and **do not assume that the submitter already knows how to add a test or run a benchmark**. + +Specific performance optimization techniques, coding styles and conventions change over time. The first impression you give to a new contributor never does. + +Nits (requests for small changes that are not essential) are fine, but try to avoid stalling the pull request. Most nits can typically be fixed by the Foundry maintainers merging the pull request, but they can also be an opportunity for the contributor to learn a bit more about the project. + +It is always good to clearly indicate nits when you comment, e.g.: `Nit: change foo() to bar(). But this is not blocking`. + +If your comments were addressed but were not folded after new commits, or if they proved to be mistaken, please, [hide them][hiding-a-comment] with the appropriate reason to keep the conversation flow concise and relevant. + +##### Be aware of the person behind the code + +Be aware that _how_ you communicate requests and reviews in your feedback can have a significant impact on the success of the pull request. Yes, we may merge a particular change that makes Foundry better, but the individual might just not want to have anything to do with Foundry ever again. The goal is not just having good code. + +##### Abandoned or stale pull requests + +If a pull request appears to be abandoned or stalled, it is polite to first check with the contributor to see if they intend to continue the work before checking if they would mind if you took it over (especially if it just has nits left). When doing so, it is courteous to give the original contributor credit for the work they started, either by preserving their name and e-mail address in the commit log, or by using the `Author: ` or `Co-authored-by: ` metadata tag in the commits. + +_Adapted from the [ethers-rs contributing guide](https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md)_. + +### Releasing + +Releases are automatically done by the release workflow when a tag is pushed, however, these steps still need to be taken: + +1. Ensure that the versions in the relevant `Cargo.toml` files are up-to-date. +2. Update documentation links +3. Perform a final audit for breaking changes. + +[rust-coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md +[dev-tg]: https://t.me/foundry_rs +[foundry-book]: https://github.com/foundry-rs/foundry-book +[support-tg]: https://t.me/foundry_support +[mcve]: https://stackoverflow.com/help/mcve +[hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment \ No newline at end of file diff --git a/lib/forge-std/src/interfaces/IERC7540.sol b/lib/forge-std/src/interfaces/IERC7540.sol new file mode 100644 index 00000000..e42d1def --- /dev/null +++ b/lib/forge-std/src/interfaces/IERC7540.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +import "./IERC7575.sol"; + +/// @dev Interface of the base operator logic of ERC7540, as defined in +/// https://eips.ethereum.org/EIPS/eip-7540 +interface IERC7540Operator { + /** + * @dev The event emitted when an operator is set. + * + * @param controller The address of the controller. + * @param operator The address of the operator. + * @param approved The approval status. + */ + event OperatorSet(address indexed controller, address indexed operator, bool approved); + + /** + * @dev Sets or removes an operator for the caller. + * + * @param operator The address of the operator. + * @param approved The approval status. + * @return Whether the call was executed successfully or not + */ + function setOperator(address operator, bool approved) external returns (bool); + + /** + * @dev Returns `true` if the `operator` is approved as an operator for an `controller`. + * + * @param controller The address of the controller. + * @param operator The address of the operator. + * @return status The approval status + */ + function isOperator(address controller, address operator) external view returns (bool status); +} + +/// @dev Interface of the asynchronous deposit Vault interface of ERC7540, as defined in +/// https://eips.ethereum.org/EIPS/eip-7540 +interface IERC7540Deposit is IERC7540Operator { + event DepositRequest( + address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets + ); + /** + * @dev Transfers assets from sender into the Vault and submits a Request for asynchronous deposit. + * + * - MUST support ERC-20 approve / transferFrom on asset as a deposit Request flow. + * - MUST revert if all of assets cannot be requested for deposit. + * - owner MUST be msg.sender unless some unspecified explicit approval is given by the caller, + * approval of ERC-20 tokens from owner to sender is NOT enough. + * + * @param assets the amount of deposit assets to transfer from owner + * @param controller the controller of the request who will be able to operate the request + * @param owner the source of the deposit assets + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault's underlying asset token. + */ + + function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId); + + /** + * @dev Returns the amount of requested assets in Pending state. + * + * - MUST NOT include any assets in Claimable state for deposit or mint. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. + */ + function pendingDepositRequest(uint256 requestId, address controller) + external + view + returns (uint256 pendingAssets); + + /** + * @dev Returns the amount of requested assets in Claimable state for the controller to deposit or mint. + * + * - MUST NOT include any assets in Pending state. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. + */ + function claimableDepositRequest(uint256 requestId, address controller) + external + view + returns (uint256 claimableAssets); + + /** + * @dev Mints shares Vault shares to receiver by claiming the Request of the controller. + * + * - MUST emit the Deposit event. + * - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. + */ + function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares); + + /** + * @dev Mints exactly shares Vault shares to receiver by claiming the Request of the controller. + * + * - MUST emit the Deposit event. + * - controller MUST equal msg.sender unless the controller has approved the msg.sender as an operator. + */ + function mint(uint256 shares, address receiver, address controller) external returns (uint256 assets); +} + +/// @dev Interface of the asynchronous deposit Vault interface of ERC7540, as defined in +/// https://eips.ethereum.org/EIPS/eip-7540 +interface IERC7540Redeem is IERC7540Operator { + event RedeemRequest( + address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets + ); + + /** + * @dev Assumes control of shares from sender into the Vault and submits a Request for asynchronous redeem. + * + * - MUST support a redeem Request flow where the control of shares is taken from sender directly + * where msg.sender has ERC-20 approval over the shares of owner. + * - MUST revert if all of shares cannot be requested for redeem. + * + * @param shares the amount of shares to be redeemed to transfer from owner + * @param controller the controller of the request who will be able to operate the request + * @param owner the source of the shares to be redeemed + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault's share token. + */ + function requestRedeem(uint256 shares, address controller, address owner) external returns (uint256 requestId); + + /** + * @dev Returns the amount of requested shares in Pending state. + * + * - MUST NOT include any shares in Claimable state for redeem or withdraw. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. + */ + function pendingRedeemRequest(uint256 requestId, address controller) + external + view + returns (uint256 pendingShares); + + /** + * @dev Returns the amount of requested shares in Claimable state for the controller to redeem or withdraw. + * + * - MUST NOT include any shares in Pending state for redeem or withdraw. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. + */ + function claimableRedeemRequest(uint256 requestId, address controller) + external + view + returns (uint256 claimableShares); +} + +/// @dev Interface of the fully asynchronous Vault interface of ERC7540, as defined in +/// https://eips.ethereum.org/EIPS/eip-7540 +interface IERC7540 is IERC7540Deposit, IERC7540Redeem, IERC7575 {} diff --git a/lib/forge-std/src/interfaces/IERC7575.sol b/lib/forge-std/src/interfaces/IERC7575.sol new file mode 100644 index 00000000..bbef052e --- /dev/null +++ b/lib/forge-std/src/interfaces/IERC7575.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.6.2; + +import "./IERC165.sol"; + +/// @dev Interface of the ERC7575 "Multi-Asset ERC-4626 Vaults", as defined in +/// https://eips.ethereum.org/EIPS/eip-7575 +interface IERC7575 is IERC165 { + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + event Withdraw( + address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the address of the share token + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function share() external view returns (address shareTokenAddress); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} + +/// @dev Interface of the ERC20 share token, as defined in +/// https://eips.ethereum.org/EIPS/eip-7575 +interface IERC7575Share is IERC165 { + event VaultUpdate(address indexed asset, address vault); + + /** + * @dev Returns the address of the Vault for the given asset. + * + * @param asset the ERC-20 token to deposit with into the Vault + */ + function vault(address asset) external view returns (address); +} diff --git a/lib/openzeppelin-contracts/.changeset/fast-coats-try.md b/lib/openzeppelin-contracts/.changeset/fast-coats-try.md new file mode 100644 index 00000000..39ba7b8a --- /dev/null +++ b/lib/openzeppelin-contracts/.changeset/fast-coats-try.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Initializable`: Add `_initializableStorageSlot` function that returns a pointer to the storage struct. The function allows customizing with a custom storage slot with an `override`. diff --git a/lib/openzeppelin-contracts/.changeset/good-cameras-rush.md b/lib/openzeppelin-contracts/.changeset/good-cameras-rush.md new file mode 100644 index 00000000..ebe663c7 --- /dev/null +++ b/lib/openzeppelin-contracts/.changeset/good-cameras-rush.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`EnumerableMap`: Add `clear` function to EnumerableMaps which deletes all entries in the map. diff --git a/lib/openzeppelin-contracts/.changeset/gorgeous-apes-jam.md b/lib/openzeppelin-contracts/.changeset/gorgeous-apes-jam.md new file mode 100644 index 00000000..14ca3522 --- /dev/null +++ b/lib/openzeppelin-contracts/.changeset/gorgeous-apes-jam.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`TimelockController`: Receive function is now virtual. diff --git a/lib/openzeppelin-contracts/.changeset/sixty-tips-wink.md b/lib/openzeppelin-contracts/.changeset/sixty-tips-wink.md new file mode 100644 index 00000000..35c14cb8 --- /dev/null +++ b/lib/openzeppelin-contracts/.changeset/sixty-tips-wink.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`EnumerableSet`: Add `clear` function to EnumerableSets which deletes all values in the set. diff --git a/lib/openzeppelin-contracts/.githooks/pre-push b/lib/openzeppelin-contracts/.githooks/pre-push new file mode 100644 index 00000000..f028ce58 --- /dev/null +++ b/lib/openzeppelin-contracts/.githooks/pre-push @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [ "${CI:-"false"}" != "true" ]; then + npm run test:generation + npm run lint +fi diff --git a/lib/openzeppelin-contracts/scripts/prepare.sh b/lib/openzeppelin-contracts/scripts/prepare.sh new file mode 100644 index 00000000..a7d74227 --- /dev/null +++ b/lib/openzeppelin-contracts/scripts/prepare.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if git status &>/dev/null; then git config core.hooksPath .githooks; fi diff --git a/lib/openzeppelin-contracts/test/helpers/erc4337-entrypoint.js b/lib/openzeppelin-contracts/test/helpers/erc4337-entrypoint.js new file mode 100644 index 00000000..aba49f4c --- /dev/null +++ b/lib/openzeppelin-contracts/test/helpers/erc4337-entrypoint.js @@ -0,0 +1,31 @@ +const { ethers } = require('hardhat'); +const { setCode } = require('@nomicfoundation/hardhat-network-helpers'); +const fs = require('fs'); +const path = require('path'); + +const INSTANCES = { + entrypoint: { + address: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../bin/EntryPoint070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../bin/EntryPoint070.bytecode'), 'hex'), + }, + sendercreator: { + address: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../bin/SenderCreator070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../bin/SenderCreator070.bytecode'), 'hex'), + }, +}; + +function deployEntrypoint() { + return Promise.all( + Object.entries(INSTANCES).map(([name, { address, abi, bytecode }]) => + setCode(address, '0x' + bytecode.replace(/0x/, '')) + .then(() => ethers.getContractAt(abi, address)) + .then(instance => ({ [name]: instance })), + ), + ).then(namedInstances => Object.assign(...namedInstances)); +} + +module.exports = { + deployEntrypoint, +}; diff --git a/lib/openzeppelin-contracts/test/helpers/precompiles.js b/lib/openzeppelin-contracts/test/helpers/precompiles.js new file mode 100644 index 00000000..fb6b7132 --- /dev/null +++ b/lib/openzeppelin-contracts/test/helpers/precompiles.js @@ -0,0 +1,12 @@ +module.exports = { + ecRecover: '0x0000000000000000000000000000000000000001', + SHA2_256: '0x0000000000000000000000000000000000000002', + RIPEMD_160: '0x0000000000000000000000000000000000000003', + identity: '0x0000000000000000000000000000000000000004', + modexp: '0x0000000000000000000000000000000000000005', + ecAdd: '0x0000000000000000000000000000000000000006', + ecMul: '0x0000000000000000000000000000000000000007', + ecPairing: '0x0000000000000000000000000000000000000008', + blake2f: '0x0000000000000000000000000000000000000009', + pointEvaluation: '0x000000000000000000000000000000000000000a', +}; diff --git a/lib/solady/prep/memory-safe-scan.js b/lib/solady/prep/memory-safe-scan.js new file mode 100644 index 00000000..c63c3edb --- /dev/null +++ b/lib/solady/prep/memory-safe-scan.js @@ -0,0 +1,29 @@ +#!/usr/bin/env node +const { readSync, forEachWalkSync, hasAnyPathSequence } = require('./common.js'); + +async function main() { + const pathSequencesToIgnore = ['g', 'legacy']; + + const loggedSrcPaths = []; + forEachWalkSync(['src'], srcPath => { + if (!srcPath.match(/\.sol$/i)) return; + if (hasAnyPathSequence(srcPath, pathSequencesToIgnore)) return; + + const src = readSync(srcPath); + const assemblyTagRe = /(\/\/\/\s*?@solidity\s*?memory-safe-assembly\s+?)?assembly\s*?(\(.*?\))?\{/gm; + for (let m = null; (m = assemblyTagRe.exec(src)) !== null; ) { + if ((m[0] + '').indexOf('memory-safe') === -1) { + if (loggedSrcPaths.indexOf(srcPath) === -1) { + loggedSrcPaths.push(srcPath); + console.log(srcPath + ':'); + } + console.log(' line:', src.slice(0, m.index).split(/\n/).length); + } + } + }); +}; + +main().catch(e => { + console.error(e); + process.exit(1); +}); diff --git a/lib/solady/src/accounts/LibEIP7702.sol b/lib/solady/src/accounts/LibEIP7702.sol new file mode 100644 index 00000000..a8963673 --- /dev/null +++ b/lib/solady/src/accounts/LibEIP7702.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @notice Library for EIP7702 operations. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/accounts/LibEIP7702.sol) +library LibEIP7702 { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The proxy query has failed. + error ProxyQueryFailed(); + + /// @dev Failed to change the proxy admin. + error ChangeProxyAdminFailed(); + + /// @dev Failed to upgrade the proxy. + error UpgradeProxyFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ERC-1967 storage slot for the implementation in the proxy. + /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. + bytes32 internal constant ERC1967_IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /// @dev The transient storage slot for requesting the proxy to initialize the implementation. + /// `uint256(keccak256("eip7702.proxy.delegation.initialization.request")) - 1`. + /// While we would love to use a smaller constant, this slot is used in both the proxy + /// and the delegation, so we shall just use bytes32 in case we want to standardize this. + bytes32 internal constant EIP7702_PROXY_DELEGATION_INITIALIZATION_REQUEST_SLOT = + 0x94e11c6e41e7fb92cb8bb65e13fdfbd4eba8b831292a1a220f7915c78c7c078f; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* AUTHORITY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the delegation of the account. + /// If the account is not an EIP7702 authority, the `delegation` will be `address(0)`. + function delegation(address account) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + extcodecopy(account, 0x00, 0x00, 0x20) + // Note: Checking that it starts with hex"ef01" is the most general and futureproof. + // 7702 bytecode is `abi.encodePacked(hex"ef01", uint8(version), address(delegation))`. + result := mul(shr(96, mload(0x03)), eq(0xef01, shr(240, mload(0x00)))) + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the implementation of the proxy. + /// Assumes that the proxy is a proper EIP7702Proxy, if it exists. + function proxyImplementation(address proxy) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + // Although `implementation()` is supported, we'll use a less common + // function selector to avoid accidental collision with other delegations. + mstore(0x00, 0x7dae87cb) // `eip7702ProxyImplementation()`. + let t := staticcall(gas(), proxy, 0x1c, 0x04, 0x00, 0x20) + if iszero(and(gt(returndatasize(), 0x1f), t)) { + mstore(0x00, 0x26ec9b6a) // `ProxyQueryFailed()`. + revert(0x1c, 0x04) + } + result := mload(0x00) + } + } + + /// @dev Returns the admin of the proxy. + /// Assumes that the proxy is a proper EIP7702Proxy, if it exists. + function proxyAdmin(address proxy) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0xf851a440) // `admin()`. + let t := staticcall(gas(), proxy, 0x1c, 0x04, 0x00, 0x20) + if iszero(and(gt(returndatasize(), 0x1f), t)) { + mstore(0x00, 0x26ec9b6a) // `ProxyQueryFailed()`. + revert(0x1c, 0x04) + } + result := mload(0x00) + } + } + + /// @dev Changes the admin on the proxy. The caller must be the admin. + /// Assumes that the proxy is a proper EIP7702Proxy, if it exists. + function changeProxyAdmin(address proxy, address newAdmin) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x8f283970) // `changeAdmin(address)`. + mstore(0x20, newAdmin) // The implementation will clean the upper 96 bits. + if iszero(and(eq(mload(0x00), 1), call(gas(), proxy, 0, 0x1c, 0x24, 0x00, 0x20))) { + mstore(0x00, 0xc502e37e) // `ChangeProxyAdminFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Changes the implementation on the proxy. The caller must be the admin. + /// Assumes that the proxy is a proper EIP7702Proxy, if it exists. + function upgradeProxy(address proxy, address newImplementation) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x0900f010) // `upgrade(address)`. + mstore(0x20, newImplementation) // The implementation will clean the upper 96 bits. + if iszero(and(eq(mload(0x00), 1), call(gas(), proxy, 0, 0x1c, 0x24, 0x00, 0x20))) { + mstore(0x00, 0xc6edd882) // `UpgradeProxyFailed()`. + revert(0x1c, 0x04) + } + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* PROXY DELEGATION OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Upgrades the implementation. + /// The new implementation will NOT be active until the next UserOp or transaction. + /// To "auto-upgrade" to the latest implementation on the proxy, pass in `address(0)` to reset + /// the implementation slot. This causes the proxy to use the latest default implementation, + /// which may be optionally reinitialized via `requestProxyDelegationInitialization()`. + /// This function is intended to be used on the authority of an EIP7702Proxy delegation. + /// The most intended usage pattern is to wrap this in an access-gated admin function. + function upgradeProxyDelegation(address newImplementation) internal { + /// @solidity memory-safe-assembly + assembly { + let s := ERC1967_IMPLEMENTATION_SLOT + // Preserve the upper 96 bits when updating in case they are used for some stuff. + mstore(0x00, sload(s)) + mstore(0x0c, shl(96, newImplementation)) + sstore(s, mload(0x00)) + } + } + + /// @dev Requests the implementation to be initialized to the latest implementation on the proxy. + /// This function is intended to be used on the authority of an EIP7702Proxy delegation. + /// The most intended usage pattern is to place it at the end of an `execute` function. + function requestProxyDelegationInitialization() internal { + /// @solidity memory-safe-assembly + assembly { + if iszero(shl(96, sload(ERC1967_IMPLEMENTATION_SLOT))) { + // Use a dedicated transient storage slot for better Swiss-cheese-model safety. + tstore(EIP7702_PROXY_DELEGATION_INITIALIZATION_REQUEST_SLOT, address()) + } + } + } +} diff --git a/scripts/batchDeploymentScript/SystemConfigManager.sol b/scripts/batchDeploymentScript/SystemConfigManager.sol index fd406b9d..fe195c1a 100644 --- a/scripts/batchDeploymentScript/SystemConfigManager.sol +++ b/scripts/batchDeploymentScript/SystemConfigManager.sol @@ -51,7 +51,7 @@ contract SystemConfigManager is Initializable { require(vars.ownerAddress != address(0), "Owner address cannot be zero"); // Initialize Basic Contract's params - EventValidator(batchAddressesSet.batch1Addrs.eventValidator).initialize(vars.crossL2ProverAddress); + DefaultReserveInterestRateStrategy(batchAddressesSet.batch1Addrs.defaultReserveInterestRateStrategy).initialize( strategyParams.lendingPoolAddressesProvider, strategyParams.optimalUtilizationRate, @@ -104,7 +104,8 @@ contract SystemConfigManager is Initializable { batchAddressesSet.batch4Addrs.routerImpl, batchAddressesSet.batch1Addrs.proxyAdmin, initData ) ); - + + EventValidator(batchAddressesSet.batch1Addrs.eventValidator).initialize(vars.crossL2ProverAddress,proxyRouter,proxyLp); vars.lpProvider.setRouter(proxyRouter); // Initialize the LendingPool. diff --git a/scripts/batchDeploymentScript/batchDeploy.s.sol b/scripts/batchDeploymentScript/batchDeploy.s.sol index f1c53143..132a25e5 100644 --- a/scripts/batchDeploymentScript/batchDeploy.s.sol +++ b/scripts/batchDeploymentScript/batchDeploy.s.sol @@ -111,12 +111,12 @@ contract MainDeployer is Script { SystemConfigManager systemConfigManager; function deployBatches() internal { - string memory systemConfigManagerSalt = "systemConfigManager3"; + string memory systemConfigManagerSalt = "systemConfigManager4"; string memory batchDeployer1Salt = "batchDeployer1"; string memory batchDeployer2Salt = "batchDeployer2"; string memory batchDeployer3Salt = "batchDeployer3"; string memory batchDeployer4Salt = "batchDeployer4"; - + console.log("deploying systemConfigManager..."); systemConfigManager = SystemConfigManager( Create2Helper.deployContractWithArgs( "SystemConfigManager", @@ -347,14 +347,7 @@ contract MainDeployer is Script { console.log(" LendingPoolCollateralManager:", batchAddressesSet.batch4Addrs.lendingPoolCollateralManager); console.log(" Router :", batchAddressesSet.batch4Addrs.proxyRouter); - - console.log( - " DefaultReserveInterestRateStrategy:", batchAddressesSet.batch1Addrs.defaultReserveInterestRateStrategy - ); - console.log(" EventValidator:", batchAddressesSet.batch1Addrs.eventValidator); console.log(" ProxyAdmin:", batchAddressesSet.batch1Addrs.proxyAdmin); - - console.log(" MockPriceOracle:", batchAddressesSet.batch1Addrs.mockPriceOracle); console.log(" VariableDebtToken:", batchAddressesSet.batch3Addrs.variableDebtToken); string memory deploymentFile = "deployment.json"; @@ -375,13 +368,7 @@ contract MainDeployer is Script { vm.serializeAddress( obj, "LendingPoolCollateralManager", batchAddressesSet.batch4Addrs.lendingPoolCollateralManager ); - vm.serializeAddress( - obj, "DefaultReserveInterestRateStrategy", batchAddressesSet.batch1Addrs.defaultReserveInterestRateStrategy - ); - vm.serializeAddress(obj, "MockPriceOracle", batchAddressesSet.batch1Addrs.mockPriceOracle); - vm.serializeAddress(obj, "EventValidator", batchAddressesSet.batch1Addrs.eventValidator); vm.serializeAddress(obj, "ProxyAdmin", batchAddressesSet.batch1Addrs.proxyAdmin); - string memory jsonOutput = vm.serializeAddress(obj, "Owner", vm.parseTomlAddress(deployConfig, ".owner.address")); vm.writeJson(jsonOutput, deploymentFile); diff --git a/src/RVaultAsset.sol b/src/RVaultAsset.sol index 1cc3f737..508c54c4 100644 --- a/src/RVaultAsset.sol +++ b/src/RVaultAsset.sol @@ -142,17 +142,20 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { _bridge(receiverOfUnderlying, toChainId, amount); } else { _burn(msg.sender, amount); - _handleCurrentChainBurn(receiverOfUnderlying, amount); + handleCurentChainTransfer(receiverOfUnderlying, amount); } } - function _handleCurrentChainBurn(address receiverOfUnderlying, uint256 amount) internal { + function handleCurentChainTransfer(address receiverOfUnderlying, uint256 amount) internal { uint256 totalUnderylying = totalAssets(); uint256 sendAmount; // when rVaultAsset does not have enough uderlying due to bridging if (totalUnderylying >= amount) sendAmount = amount; else sendAmount = totalUnderylying; + uint256 rVaultToBeMinted = amount - sendAmount; + if (rVaultToBeMinted > 0) super._mint(receiverOfUnderlying, rVaultToBeMinted); + if (pool_type == 1) { ISuperAsset(underlying).withdraw(receiverOfUnderlying, sendAmount); } else { @@ -244,17 +247,7 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) { revert OnlyPeer(_origin.srcEid, _origin.sender); } - - uint256 payAmount = amount; - if (totalAssets() < amount) { - payAmount = totalAssets(); - super._mint(receiverOfUnderlying, amount - payAmount); - } - if (pool_type == 1) { - ISuperAsset(underlying).withdraw(receiverOfUnderlying, payAmount); - } else { - IERC20(underlying).safeTransfer(receiverOfUnderlying, payAmount); - } + handleCurentChainTransfer(receiverOfUnderlying, amount); emit OFTReceived(_guid, _origin.srcEid, address(0), 0); } @@ -270,7 +263,7 @@ contract RVaultAsset is Initializable, SuperOwnable, OFT { (SendParam memory sendParam, MessagingFee memory fee) = getFeeQuote(receiverOfUnderlying, toChainId, amount); _send(sendParam, fee, payable(address(this))); } else { - _handleCurrentChainBurn(receiverOfUnderlying, amount); + handleCurentChainTransfer(receiverOfUnderlying, amount); } } diff --git a/src/Router.sol b/src/Router.sol index 4b5e5f61..e46d76b4 100644 --- a/src/Router.sol +++ b/src/Router.sol @@ -73,12 +73,8 @@ contract Router is Initializable, SuperPausable { ) external whenNotPaused { if (ILendingPoolAddressesProvider(addressesProvider).getRelayerStatus(msg.sender) != true) revert NOT_RELAYER(); - if (_mode == ValidationMode.CROSS_L2_PROVER_RECEIPT) { - EventValidator(eventValidator).validate(_mode, _identifier[0], _data, _logIndex, _proof); - } - for (uint256 i = 0; i < _identifier.length; i++) { - if (_mode != ValidationMode.CUSTOM && _mode != ValidationMode.CROSS_L2_PROVER_RECEIPT) { + if (_mode != ValidationMode.CUSTOM ) { EventValidator(eventValidator).validate(_mode, _identifier[i], _data, _logIndex, _proof); } diff --git a/src/interfaces/ICrossL2Prover.sol b/src/interfaces/ICrossL2Prover.sol index fdba6fa4..b84d1f30 100644 --- a/src/interfaces/ICrossL2Prover.sol +++ b/src/interfaces/ICrossL2Prover.sol @@ -1,16 +1,42 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.25; +/** + * @title ICrossL2Prover + * @author Polymer Labs + * @notice A contract that can prove peptides state. Since peptide is an aggregator of many chains' states, this + * contract can in turn be used to prove any arbitrary events and/or storage on counterparty chains. + */ interface ICrossL2Prover { - function validateEvent(uint256 logIndex, bytes calldata proof) - external - returns (bytes32 chainId, address emittingContract, bytes[] memory topics, bytes memory unindexedData); - function validateReceipt(bytes calldata proof) + /** + * @notice A a log at a given raw rlp encoded receipt at a given logIndex within the receipt. + * @notice the receiptRLP should first be validated by calling validateReceipt. + * @param proof: The proof of a given rlp bytes for the receipt, returned from the receipt MMPT of a block. + * @return chainId The chainID that the proof proves the log for + * @return emittingContract The address of the contract that emitted the log on the source chain + * @return topics The topics of the event. First topic is the event signature that can be calculated by + * Event.selector. The remaining elements in this array are the indexed parameters of the event. + * @return unindexedData // The abi encoded non-indexed parameters of the event. + */ + function validateEvent(bytes calldata proof) external view - returns (bytes32 chainID, bytes memory rlpEncodedBytes); - function parseLog(uint256 logIndex, bytes calldata rlpEncodedBytes) + returns (uint32 chainId, address emittingContract, bytes calldata topics, bytes calldata unindexedData); + + /** + * Return srcChain, Block Number, Receipt Index, and Local Index for a requested proof + */ + function inspectLogIdentifier(bytes calldata proof) external - view - returns (address emittingContract, bytes[] memory topics, bytes memory unindexedData); -} + pure + returns (uint32 srcChain, uint64 blockNumber, uint16 receiptIndex, uint8 logIndex); + + /** + * Return polymer state root, height , and signature over height and root which can be verified by + * crypto.pubkey(keccak(peptideStateRoot, peptideHeight)) + */ + function inspectPolymerState(bytes calldata proof) + external + pure + returns (bytes32 stateRoot, uint64 height, bytes memory signature); +} \ No newline at end of file diff --git a/src/libraries/EventValidator.sol b/src/libraries/EventValidator.sol index ee064ad6..64597c65 100644 --- a/src/libraries/EventValidator.sol +++ b/src/libraries/EventValidator.sol @@ -11,19 +11,29 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; enum ValidationMode { CUSTOM, CROSS_L2_INBOX, - CROSS_L2_PROVER_EVENT, - CROSS_L2_PROVER_RECEIPT + CROSS_L2_PROVER_EVENT } + contract EventValidator is Initializable, Ownable { ICrossL2Prover private crossL2Prover; + address router; + address lendingPool; + + event logger(string message); + event loggerUint(uint256); + event loggerBytes32(bytes32); + event loggerBytes(bytes); constructor(address ownerAddr) Ownable(ownerAddr) { _transferOwnership(ownerAddr); } - function initialize(address _crossL2Prover) external initializer onlyOwner { + function initialize(address _crossL2Prover, address _router,address _lendingPool) external initializer onlyOwner { crossL2Prover = ICrossL2Prover(_crossL2Prover); + router=_router; + lendingPool=_lendingPool; + } function validate( @@ -33,37 +43,53 @@ contract EventValidator is Initializable, Ownable { uint256[] calldata _logIndex, bytes calldata _proof ) external { + /// @dev use ICrossL2Inbox to validate message if (_mode == ValidationMode.CROSS_L2_INBOX) { if (_identifier.origin != address(this)) { revert("!origin"); } - ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_identifier, keccak256(_data[0])); + ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage( + _identifier, + keccak256(_data[0]) + ); } - if (_mode == ValidationMode.CROSS_L2_PROVER_EVENT) { + else if (_mode == ValidationMode.CROSS_L2_PROVER_EVENT) { /// @dev use ICrossL2Prover to validate message - (, address emittingContract, bytes[] memory topics, bytes memory unindexedData) = - crossL2Prover.validateEvent(_logIndex[0], _proof); - if (emittingContract != address(this)) { - revert("!origin"); - } - if (keccak256(abi.encode(topics[0], unindexedData)) != keccak256(_data[0])) { - revert("!data"); - } - } - if (_mode == ValidationMode.CROSS_L2_PROVER_RECEIPT) { - /// @dev use ICrossL2Prover to validate receipt - (, bytes memory rlpEncodedBytes) = crossL2Prover.validateReceipt(_proof); - for (uint256 i = 0; i < _logIndex.length; i++) { - (address emittingContract, bytes[] memory topics, bytes memory unindexedData) = - crossL2Prover.parseLog(_logIndex[i], rlpEncodedBytes); - if (emittingContract != address(this)) { - revert("!origin"); - } - if (keccak256(abi.encode(topics[0], unindexedData)) != keccak256(_data[i])) { - revert("!data"); + ( + , + address sourceContract, + bytes memory topics, + bytes memory unindexedData + ) = crossL2Prover.validateEvent(_proof); + + // Step 2: Split concatenated topics into individual 32-byte values + bytes32[] memory topicsArray = new bytes32[](3); // [eventSig, sender, hashedKey] + + // // Use assembly for efficient memory operations when splitting topics + assembly { + // Skip first 32 bytes (length prefix of bytes array) + let topicsPtr := add(topics, 32) + + // Load each 32-byte topic into the array + // topicsArray structure: [eventSig, sender, hashedKey] + for { + let i := 0 + } lt(i, 3) { + i := add(i, 1) + } { + mstore( + add(add(topicsArray, 32), mul(i, 32)), + mload(add(topicsPtr, mul(i, 32))) + ) } } + + require(topicsArray[0] == bytes32(_data[0]), "Invalid event signature"); + bytes memory dataSlice = _data[0][32:]; + require(keccak256(unindexedData) == keccak256(dataSlice), "Malformed data"); + require (sourceContract == lendingPool || sourceContract == router,"Malformed origin"); + } } } diff --git a/test/Base.t.sol b/test/Base.t.sol index 240c24f8..7d53c94d 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -190,8 +190,6 @@ contract Base is TestHelperOz5 { // ################ Deploy Components ################ vm.prank(owner); eventValidator = new EventValidator(owner); - vm.prank(owner); - eventValidator.initialize(supportedChains[0].crossL2Prover); vm.prank(owner); proxyAdmin = address(new ProxyAdmin{salt: "proxyAdmin"}(owner)); @@ -244,6 +242,11 @@ contract Base is TestHelperOz5 { router.initialize(address(proxyLp), address(lpAddressProvider1), address(eventValidator)); vm.deal(address(router), 100 ether); + + vm.prank(owner); + eventValidator.initialize(supportedChains[0].crossL2Prover, address(router), address(proxyLp)); + + vm.label(address(lpAddressProvider1), "lpAddressProvider1"); vm.label(address(lpAddressProvider2), "lpAddressProvider2"); From 3d2d1326376ae844972c7fa191539a063b3d9b0a Mon Sep 17 00:00:00 2001 From: 0xumarkhatab Date: Wed, 2 Apr 2025 17:55:57 +0500 Subject: [PATCH 4/5] migrated from `testFail` convention in tests --- .../SystemConfigManager.sol | 6 ++- src/Router.sol | 2 +- src/interfaces/ICrossL2Prover.sol | 2 +- src/interop-std/src/auth/SuperOwnable.sol | 2 +- src/libraries/EventValidator.sol | 39 +++++-------------- test/Base.t.sol | 2 - test/RVaultAsset/RVaultAssetTestMisc.t.sol | 5 +-- test/SuperAsset/SuperAssetTestDeposit.t.sol | 8 ++-- test/SuperAsset/SuperAssetTestWithdraw.t.sol | 4 +- 9 files changed, 25 insertions(+), 45 deletions(-) diff --git a/scripts/batchDeploymentScript/SystemConfigManager.sol b/scripts/batchDeploymentScript/SystemConfigManager.sol index fe195c1a..8551c1d3 100644 --- a/scripts/batchDeploymentScript/SystemConfigManager.sol +++ b/scripts/batchDeploymentScript/SystemConfigManager.sol @@ -104,8 +104,10 @@ contract SystemConfigManager is Initializable { batchAddressesSet.batch4Addrs.routerImpl, batchAddressesSet.batch1Addrs.proxyAdmin, initData ) ); - - EventValidator(batchAddressesSet.batch1Addrs.eventValidator).initialize(vars.crossL2ProverAddress,proxyRouter,proxyLp); + + EventValidator(batchAddressesSet.batch1Addrs.eventValidator).initialize( + vars.crossL2ProverAddress, proxyRouter, proxyLp + ); vars.lpProvider.setRouter(proxyRouter); // Initialize the LendingPool. diff --git a/src/Router.sol b/src/Router.sol index e46d76b4..c696bf2a 100644 --- a/src/Router.sol +++ b/src/Router.sol @@ -74,7 +74,7 @@ contract Router is Initializable, SuperPausable { if (ILendingPoolAddressesProvider(addressesProvider).getRelayerStatus(msg.sender) != true) revert NOT_RELAYER(); for (uint256 i = 0; i < _identifier.length; i++) { - if (_mode != ValidationMode.CUSTOM ) { + if (_mode != ValidationMode.CUSTOM) { EventValidator(eventValidator).validate(_mode, _identifier[i], _data, _logIndex, _proof); } diff --git a/src/interfaces/ICrossL2Prover.sol b/src/interfaces/ICrossL2Prover.sol index b84d1f30..0d9c5459 100644 --- a/src/interfaces/ICrossL2Prover.sol +++ b/src/interfaces/ICrossL2Prover.sol @@ -39,4 +39,4 @@ interface ICrossL2Prover { external pure returns (bytes32 stateRoot, uint64 height, bytes memory signature); -} \ No newline at end of file +} diff --git a/src/interop-std/src/auth/SuperOwnable.sol b/src/interop-std/src/auth/SuperOwnable.sol index a19be293..0a1d3cfe 100644 --- a/src/interop-std/src/auth/SuperOwnable.sol +++ b/src/interop-std/src/auth/SuperOwnable.sol @@ -42,7 +42,7 @@ abstract contract SuperOwnable is Ownable, ISemver { /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ modifier onlySuperAdmin() { - if (msg.sender == owner() && block.chainid != superAdminChainId) revert ChainNotSuperAdmin(); + if (!(msg.sender == owner() && block.chainid == superAdminChainId)) revert ChainNotSuperAdmin(); _; } diff --git a/src/libraries/EventValidator.sol b/src/libraries/EventValidator.sol index 64597c65..baf5e2ee 100644 --- a/src/libraries/EventValidator.sol +++ b/src/libraries/EventValidator.sol @@ -14,7 +14,6 @@ enum ValidationMode { CROSS_L2_PROVER_EVENT } - contract EventValidator is Initializable, Ownable { ICrossL2Prover private crossL2Prover; address router; @@ -29,11 +28,10 @@ contract EventValidator is Initializable, Ownable { _transferOwnership(ownerAddr); } - function initialize(address _crossL2Prover, address _router,address _lendingPool) external initializer onlyOwner { + function initialize(address _crossL2Prover, address _router, address _lendingPool) external initializer onlyOwner { crossL2Prover = ICrossL2Prover(_crossL2Prover); - router=_router; - lendingPool=_lendingPool; - + router = _router; + lendingPool = _lendingPool; } function validate( @@ -43,25 +41,16 @@ contract EventValidator is Initializable, Ownable { uint256[] calldata _logIndex, bytes calldata _proof ) external { - /// @dev use ICrossL2Inbox to validate message if (_mode == ValidationMode.CROSS_L2_INBOX) { if (_identifier.origin != address(this)) { revert("!origin"); } - ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage( - _identifier, - keccak256(_data[0]) - ); - } - else if (_mode == ValidationMode.CROSS_L2_PROVER_EVENT) { + ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_identifier, keccak256(_data[0])); + } else if (_mode == ValidationMode.CROSS_L2_PROVER_EVENT) { /// @dev use ICrossL2Prover to validate message - ( - , - address sourceContract, - bytes memory topics, - bytes memory unindexedData - ) = crossL2Prover.validateEvent(_proof); + (, address sourceContract, bytes memory topics, bytes memory unindexedData) = + crossL2Prover.validateEvent(_proof); // Step 2: Split concatenated topics into individual 32-byte values bytes32[] memory topicsArray = new bytes32[](3); // [eventSig, sender, hashedKey] @@ -73,23 +62,15 @@ contract EventValidator is Initializable, Ownable { // Load each 32-byte topic into the array // topicsArray structure: [eventSig, sender, hashedKey] - for { - let i := 0 - } lt(i, 3) { - i := add(i, 1) - } { - mstore( - add(add(topicsArray, 32), mul(i, 32)), - mload(add(topicsPtr, mul(i, 32))) - ) + for { let i := 0 } lt(i, 3) { i := add(i, 1) } { + mstore(add(add(topicsArray, 32), mul(i, 32)), mload(add(topicsPtr, mul(i, 32)))) } } require(topicsArray[0] == bytes32(_data[0]), "Invalid event signature"); bytes memory dataSlice = _data[0][32:]; require(keccak256(unindexedData) == keccak256(dataSlice), "Malformed data"); - require (sourceContract == lendingPool || sourceContract == router,"Malformed origin"); - + require(sourceContract == lendingPool || sourceContract == router, "Malformed origin"); } } } diff --git a/test/Base.t.sol b/test/Base.t.sol index 7d53c94d..03c5f86a 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -242,11 +242,9 @@ contract Base is TestHelperOz5 { router.initialize(address(proxyLp), address(lpAddressProvider1), address(eventValidator)); vm.deal(address(router), 100 ether); - vm.prank(owner); eventValidator.initialize(supportedChains[0].crossL2Prover, address(router), address(proxyLp)); - vm.label(address(lpAddressProvider1), "lpAddressProvider1"); vm.label(address(lpAddressProvider2), "lpAddressProvider2"); diff --git a/test/RVaultAsset/RVaultAssetTestMisc.t.sol b/test/RVaultAsset/RVaultAssetTestMisc.t.sol index 9fac76cb..381d3758 100644 --- a/test/RVaultAsset/RVaultAssetTestMisc.t.sol +++ b/test/RVaultAsset/RVaultAssetTestMisc.t.sol @@ -24,7 +24,7 @@ contract RVaultAssetTestMisc is RVaultAssetTestBase { * @dev Tests that only the super admin can modify the rVaultAsset's state. */ function test_rVaultAssetSuperAdminFunctions() public { - vm.startPrank(proxyAdmin); + vm.startPrank(owner); // Test withdrawal cooldown period modification uint256 newPeriod = 2 days; IRVaultAsset(rVaultAsset1).setWithdrawCoolDownPeriod(newPeriod); @@ -35,11 +35,10 @@ contract RVaultAssetTestMisc is RVaultAssetTestBase { /** * @dev Tests that non-admins cannot modify the rVaultAsset's state. */ - function testFail_rVaultAssetNonAdminFunctions() public { + function test_rVaultAssetNonAdminFunctionsFails() public { vm.startPrank(user1); vm.expectRevert(); IRVaultAsset(rVaultAsset1).setWithdrawCoolDownPeriod(2 days); - vm.stopPrank(); } /// @dev tests that the rVaultAsset has the correct underlying diff --git a/test/SuperAsset/SuperAssetTestDeposit.t.sol b/test/SuperAsset/SuperAssetTestDeposit.t.sol index 5c33c04d..d2bf4e31 100644 --- a/test/SuperAsset/SuperAssetTestDeposit.t.sol +++ b/test/SuperAsset/SuperAssetTestDeposit.t.sol @@ -43,18 +43,18 @@ contract SuperAssetTestDeposit is SuperAssetTestBase { ); } - function testFail_superAssetDepositInsufficientApproval() public { + function test_superAssetDepositInsufficientApprovalFails() public { // Remove approval vm.startPrank(user1); IERC20(superAsset.underlying()).approve(address(superAsset), 0); + vm.expectRevert(); superAsset.deposit(user1, DEPOSIT_AMOUNT); // Should fail due to no approval - vm.stopPrank(); } - function testFail_superAssetDepositInsufficientBalance() public { + function test_superAssetDepositInsufficientBalanceFails() public { vm.startPrank(user1); + vm.expectRevert(); superAsset.deposit(user1, INITIAL_BALANCE + 1); // Should fail as user doesn't have this much - vm.stopPrank(); } /// @notice Test that the superasset's deposit function behaves correctly diff --git a/test/SuperAsset/SuperAssetTestWithdraw.t.sol b/test/SuperAsset/SuperAssetTestWithdraw.t.sol index 38c7fce1..3f7f6b5a 100644 --- a/test/SuperAsset/SuperAssetTestWithdraw.t.sol +++ b/test/SuperAsset/SuperAssetTestWithdraw.t.sol @@ -67,9 +67,9 @@ contract SuperAssetTestWithdraw is SuperAssetTestBase { ); } - function testFail_superAssetWithdrawInsufficientBalance() public { + function test_superAssetWithdrawInsufficientBalanceFails() public { vm.startPrank(user1); + vm.expectRevert(); superAsset.withdraw(user1, 1); // Should fail as user1 has no balance - vm.stopPrank(); } } From 09630acb358af9dd424555e350bb56b8c1a0f449 Mon Sep 17 00:00:00 2001 From: 0xumarkhatab Date: Wed, 2 Apr 2025 18:38:56 +0500 Subject: [PATCH 5/5] Corrected superAdmin modifier --- src/interop-std/src/auth/SuperOwnable.sol | 3 ++- test/LendingPool/LendingPoolTestWithdraw.t.sol | 2 ++ test/RVaultAsset/RVaultAssetTestMisc.t.sol | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/interop-std/src/auth/SuperOwnable.sol b/src/interop-std/src/auth/SuperOwnable.sol index 0a1d3cfe..a06c8564 100644 --- a/src/interop-std/src/auth/SuperOwnable.sol +++ b/src/interop-std/src/auth/SuperOwnable.sol @@ -18,6 +18,7 @@ abstract contract SuperOwnable is Ownable, ISemver { error DataNotCrosschainSuperAdminChainIdUpdate(); error OwnershipNotInSync(); error NoOwnershipChange(); + error NotSuperAdmin(); error ChainNotSuperAdmin(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -42,7 +43,7 @@ abstract contract SuperOwnable is Ownable, ISemver { /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ modifier onlySuperAdmin() { - if (!(msg.sender == owner() && block.chainid == superAdminChainId)) revert ChainNotSuperAdmin(); + if (!(msg.sender == owner() && block.chainid == superAdminChainId)) revert NotSuperAdmin(); _; } diff --git a/test/LendingPool/LendingPoolTestWithdraw.t.sol b/test/LendingPool/LendingPoolTestWithdraw.t.sol index cfe47c99..a9cc70d7 100644 --- a/test/LendingPool/LendingPoolTestWithdraw.t.sol +++ b/test/LendingPool/LendingPoolTestWithdraw.t.sol @@ -371,7 +371,9 @@ contract LendingPoolTestWithdraw is LendingPoolTestBase { // ======== Cross-Chain Configuration ======== // Set chain ID mappings for cross-chain communication + vm.prank(owner); (aRVaultAsset).setChainToEid(bEid, bEid); + vm.prank(owner); (bRVaultAsset).setChainToEid(aEid, aEid); // ======== Initial Token Distribution ======== diff --git a/test/RVaultAsset/RVaultAssetTestMisc.t.sol b/test/RVaultAsset/RVaultAssetTestMisc.t.sol index 381d3758..a6906eda 100644 --- a/test/RVaultAsset/RVaultAssetTestMisc.t.sol +++ b/test/RVaultAsset/RVaultAssetTestMisc.t.sol @@ -24,9 +24,10 @@ contract RVaultAssetTestMisc is RVaultAssetTestBase { * @dev Tests that only the super admin can modify the rVaultAsset's state. */ function test_rVaultAssetSuperAdminFunctions() public { - vm.startPrank(owner); // Test withdrawal cooldown period modification uint256 newPeriod = 2 days; + vm.startPrank(vm.parseTomlAddress(deployConfig, ".owner.address")); + IRVaultAsset(rVaultAsset1).setWithdrawCoolDownPeriod(newPeriod); assertEq(IRVaultAsset(rVaultAsset1).withdrawCoolDownPeriod(), newPeriod); vm.stopPrank();