diff --git a/.gas-snapshot b/.gas-snapshot index 7dfdd05..3e0d799 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -4,9 +4,9 @@ AuthenticatorTest:test_decodeTxID_RevertsIf_LengthIsShort() (gas: 9388) AuthenticatorTest:test_decodeTxID_RevertsIf_LengthIsZero() (gas: 9599) AuthenticatorTest:test_decodeTxID_SucceedsWith32Bytes() (gas: 6825) AuthenticatorTest:test_extSignTx_RevertsIf_TxIDLengthInvalid() (gas: 30496) -AuthenticatorTest:test_extSignTx_RevertsIf_VerificationFails() (gas: 49006) -AuthenticatorTest:test_extSignTx_SucceedsAsCompletedAndEmitsEvent() (gas: 84175) -AuthenticatorTest:test_extSignTx_SucceedsAsPending() (gas: 44306) +AuthenticatorTest:test_extSignTx_RevertsIf_VerificationFails() (gas: 51274) +AuthenticatorTest:test_extSignTx_SucceedsAsCompletedAndEmitsEvent() (gas: 86442) +AuthenticatorTest:test_extSignTx_SucceedsAsPending() (gas: 46573) AuthenticatorTest:test_signTx_RevertsIf_SignerNotEqualSender() (gas: 30692) AuthenticatorTest:test_signTx_RevertsIf_SignersLengthMultiple() (gas: 60751) AuthenticatorTest:test_signTx_RevertsIf_SignersLengthZero() (gas: 22045) @@ -49,8 +49,8 @@ CrossModuleTest:test_onRecvPacket_RevertWhen_CallerLacksIbcRole() (gas: 31310) CrossModuleTest:test_onTimeoutPacket_CallsHandlerWhenCallerHasRole() (gas: 55726) CrossModuleTest:test_onTimeoutPacket_RevertWhen_CallerLacksIbcRole() (gas: 29694) CrossModuleTest:test_supportsInterface_ReturnsTrueForIIBCAndIAccessControlAndFalseForUnsupported() (gas: 8391) -CrossSimpleModuleTest:test_constructor_DoesNotGrantIbcRoleWhenDebugModeFalse() (gas: 3240600) -CrossSimpleModuleTest:test_constructor_GrantsIbcRoleWhenDebugModeTrue() (gas: 3262879) +CrossSimpleModuleTest:test_constructor_DoesNotGrantIbcRoleWhenDebugModeFalse() (gas: 3328428) +CrossSimpleModuleTest:test_constructor_GrantsIbcRoleWhenDebugModeTrue() (gas: 3350707) CrossStoreTest:test_SaveAndLoad_CoordinatorState() (gas: 119980) CrossStoreTest:test_SaveLoad_DataLossChannel0() (gas: 102862) CrossStoreTest:test_SaveLoad_DataLossExtraChannels() (gas: 98710) @@ -78,18 +78,18 @@ DelegatedLogicHandlerTest:test_getCoordinatorState_RevertIf_CalledExternally() ( DelegatedLogicHandlerTest:test_handleAcknowledgement_DelegatesToTxManager() (gas: 54817) DelegatedLogicHandlerTest:test_handlePacket_DelegatesToTxManager() (gas: 60530) DelegatedLogicHandlerTest:test_handleTimeout_DelegatesToTxManager() (gas: 53748) -DelegatedLogicHandlerTest:test_initAuthState_DelegatesToAuthManager() (gas: 74424) +DelegatedLogicHandlerTest:test_initAuthState_DelegatesToAuthManager() (gas: 74430) DelegatedLogicHandlerTest:test_isCompletedAuth_DelegatesToAuthManager() (gas: 19325) DelegatedLogicHandlerTest:test_isCompletedAuth_RevertIf_CalledExternally() (gas: 11939) DelegatedLogicHandlerTest:test_isTxRecorded_DelegatesToTxManager() (gas: 19036) DelegatedLogicHandlerTest:test_isTxRecorded_RevertIf_CalledExternally() (gas: 11697) DelegatedLogicHandlerTest:test_runTxIfCompleted_DelegatesToTxManager() (gas: 106839) -DelegatedLogicHandlerTest:test_sign_DelegatesToAuthManager() (gas: 51649) +DelegatedLogicHandlerTest:test_sign_DelegatesToAuthManager() (gas: 51655) DelegatedLogicHandlerTest:test_staticCallSelf_RevertsIf_CallFailsEmpty() (gas: 12600) DelegatedLogicHandlerTest:test_staticCallSelf_RevertsIf_OnStateChange() (gas: 1040431853) DelegatedLogicHandlerTest:test_staticCallSelf_RevertsIf_TargetReverts() (gas: 14237) DelegatedLogicHandlerTest:test_staticCallSelf_Succeeds() (gas: 9181) -DelegatedLogicHandlerTest:test_verifySignatures_DelegatesToAuthManager() (gas: 49084) +DelegatedLogicHandlerTest:test_verifySignatures_DelegatesToAuthManager() (gas: 50944) ERC20TransferModuleTest:test_decodeCallInfo_RevertWhen_InvalidLength() (gas: 11651) ERC20TransferModuleTest:test_decodeCallInfo_Success() (gas: 11807) ERC20TransferModuleTest:test_initialize_RevertWhen_AlreadyInitialized() (gas: 13202) @@ -117,17 +117,28 @@ ERC20TransferModuleTest:test_onContractPrepare_Success() (gas: 139360) IBCKeeperTest:test_exposed_initIBCKeeper_AllowsZeroAddress() (gas: 192201) IBCKeeperTest:test_exposed_initIBCKeeper_RevertOn_SecondInitialization() (gas: 12904) IBCKeeperTest:test_getIBCHandler_ReturnsSameAddress() (gas: 9955) -InitiatorTest:test_constructor_SetsChainIDHash() (gas: 8310) -InitiatorTest:test_getRequiredAccounts_AggregatesSigners() (gas: 266833) -InitiatorTest:test_getRequiredAccounts_ReturnsEmptyArrayWhenNoSigners() (gas: 69831) -InitiatorTest:test_getRequiredAccounts_ReturnsEmptyArrayWhenNoTxs() (gas: 40964) -InitiatorTest:test_initiateTx_RevertWhen_ChainIDMismatch() (gas: 61018) -InitiatorTest:test_initiateTx_RevertWhen_TimeoutHeightExpired() (gas: 58522) -InitiatorTest:test_initiateTx_RevertWhen_TimeoutTimestampExpired() (gas: 59317) -InitiatorTest:test_initiateTx_RevertWhen_txIDAlreadyExists() (gas: 155517) -InitiatorTest:test_initiateTx_SucceedsAsPendingWhenSignersNotMet() (gas: 272288) -InitiatorTest:test_initiateTx_SucceedsAsVerifiedWhenSignersMet() (gas: 309278) -InitiatorTest:test_selfXCC_ReturnsCorrectChannelInfo() (gas: 17058) +InitiatorTest:test_constructor_SetsChainIDHash() (gas: 8353) +InitiatorTest:test_getRequiredAccounts_AggregatesSigners() (gas: 270062) +InitiatorTest:test_getRequiredAccounts_ReturnsEmptyArrayWhenNoSigners() (gas: 70159) +InitiatorTest:test_getRequiredAccounts_ReturnsEmptyArrayWhenNoTxs() (gas: 41168) +InitiatorTest:test_initiateTx_RevertWhen_ChainIDMismatch() (gas: 61103) +InitiatorTest:test_initiateTx_RevertWhen_ExtensionSignatureVerificationFails() (gas: 365797) +InitiatorTest:test_initiateTx_RevertWhen_LocalSignerNotEqualSender() (gas: 198641) +InitiatorTest:test_initiateTx_RevertWhen_MixedSignersIncludeUnsupportedAuthMode() (gas: 319234) +InitiatorTest:test_initiateTx_RevertWhen_MultipleLocalSignersProvided() (gas: 276566) +InitiatorTest:test_initiateTx_RevertWhen_SignerAuthModeIsNotLocal() (gas: 189371) +InitiatorTest:test_initiateTx_RevertWhen_SignerLengthZero() (gas: 98265) +InitiatorTest:test_initiateTx_RevertWhen_TimeoutHeightExpired() (gas: 58596) +InitiatorTest:test_initiateTx_RevertWhen_TimeoutTimestampExpired() (gas: 59523) +InitiatorTest:test_initiateTx_RevertWhen_UnsupportedAuthModeMixedWithLocal() (gas: 272094) +InitiatorTest:test_initiateTx_RevertWhen_txIDAlreadyExists() (gas: 158270) +InitiatorTest:test_initiateTx_SucceedsAsPendingWhenSignersNotMet() (gas: 471312) +InitiatorTest:test_initiateTx_SucceedsAsVerifiedWhenSignersMet() (gas: 515207) +InitiatorTest:test_initiateTx_SucceedsWithExtensionSignerAsPendingWhenAuthNotCompleted() (gas: 611996) +InitiatorTest:test_initiateTx_SucceedsWithMixedExtensionAndLocalSignersAsPendingWhenAuthNotCompleted() (gas: 823085) +InitiatorTest:test_initiateTx_SucceedsWithMixedLocalAndExtensionSignersAsVerified() (gas: 857445) +InitiatorTest:test_initiateTx_SucceedsWithMultipleExtensionSignersAsVerified() (gas: 962190) +InitiatorTest:test_selfXCC_ReturnsCorrectChannelInfo() (gas: 17366) MockCrossContractTest:test_onAbort_Succeeds() (gas: 9583) MockCrossContractTest:test_onCommit_Succeeds() (gas: 9429) MockCrossContractTest:test_onContractCommitImmediately_ReturnsExpectedBytes() (gas: 15797) @@ -191,36 +202,36 @@ TxAtomicSimpleTest:test_runTx_RevertWhen_UnknownCommitProtocol() (gas: 23503) TxAtomicSimpleTest:test_runTx_SucceedsAndSendsPacket() (gas: 289827) TxAuthManagerTest:test_accountKey_ExtensionModeIgnoresOptionValue() (gas: 15975) TxAuthManagerTest:test_accountKey_GeneratesUniqueKeys() (gas: 57014) -TxAuthManagerTest:test_getAuthState_ReturnsCorrectRemainingSigners() (gas: 360320) +TxAuthManagerTest:test_getAuthState_ReturnsCorrectRemainingSigners() (gas: 360374) TxAuthManagerTest:test_getAuthState_RevertWhen_NotInitialized() (gas: 16241) -TxAuthManagerTest:test_getRemainingSigners_TracksSigningProgress() (gas: 353717) -TxAuthManagerTest:test_initAuthState_HandlesDuplicateSigners() (gas: 277966) +TxAuthManagerTest:test_getRemainingSigners_TracksSigningProgress() (gas: 353798) +TxAuthManagerTest:test_initAuthState_HandlesDuplicateSigners() (gas: 278020) TxAuthManagerTest:test_initAuthState_RevertWhen_AlreadyInitialized() (gas: 171833) -TxAuthManagerTest:test_initAuthState_Succeeds() (gas: 270899) -TxAuthManagerTest:test_initialize_RevertWhen_AlreadyInitialized() (gas: 1483796) -TxAuthManagerTest:test_initialize_RevertWhen_ArrayLengthMismatch() (gas: 1519901) -TxAuthManagerTest:test_initialize_RevertWhen_EmptyTypeUrl() (gas: 1457667) -TxAuthManagerTest:test_initialize_RevertWhen_ZeroAddressVerifier() (gas: 1395572) -TxAuthManagerTest:test_initialize_SucceedsAndEmitsEvents() (gas: 1565772) -TxAuthManagerTest:test_initialize_SucceedsWithEmptyArrays() (gas: 1392106) +TxAuthManagerTest:test_initAuthState_Succeeds() (gas: 270953) +TxAuthManagerTest:test_initialize_RevertWhen_AlreadyInitialized() (gas: 1427905) +TxAuthManagerTest:test_initialize_RevertWhen_ArrayLengthMismatch() (gas: 1464004) +TxAuthManagerTest:test_initialize_RevertWhen_EmptyTypeUrl() (gas: 1401765) +TxAuthManagerTest:test_initialize_RevertWhen_ZeroAddressVerifier() (gas: 1339669) +TxAuthManagerTest:test_initialize_SucceedsAndEmitsEvents() (gas: 1509831) +TxAuthManagerTest:test_initialize_SucceedsWithEmptyArrays() (gas: 1336241) TxAuthManagerTest:test_isCompletedAuth_ReturnsFalseWhenInitializedEmpty() (gas: 34759) TxAuthManagerTest:test_isCompletedAuth_ReturnsFalseWhenNotCompleted() (gas: 244840) TxAuthManagerTest:test_isCompletedAuth_ReturnsTrueWhenCompleted() (gas: 133928) TxAuthManagerTest:test_isCompletedAuth_RevertWhen_NotInitialized() (gas: 14387) -TxAuthManagerTest:test_setStateFromRemainingList_HandlesDuplicatesAndReturnsRemains() (gas: 261903) +TxAuthManagerTest:test_setStateFromRemainingList_HandlesDuplicatesAndReturnsRemains() (gas: 261957) TxAuthManagerTest:test_sign_FinalSignReturnsTrue() (gas: 214053) -TxAuthManagerTest:test_sign_IgnoresDuplicateSignatures() (gas: 283706) -TxAuthManagerTest:test_sign_IgnoresUnknownSigners() (gas: 211355) +TxAuthManagerTest:test_sign_IgnoresDuplicateSignatures() (gas: 283760) +TxAuthManagerTest:test_sign_IgnoresUnknownSigners() (gas: 211382) TxAuthManagerTest:test_sign_PartialSignReturnsFalse() (gas: 242956) TxAuthManagerTest:test_sign_RevertWhen_AuthAlreadyCompleted() (gas: 140603) TxAuthManagerTest:test_sign_RevertWhen_NotInitialized() (gas: 27957) -TxAuthManagerTest:test_verifySignatures_RevertWhen_AuthModeMismatch() (gas: 23650) -TxAuthManagerTest:test_verifySignatures_RevertWhen_StaticCallFails() (gas: 31433) -TxAuthManagerTest:test_verifySignatures_RevertWhen_TooManySigners() (gas: 146596) -TxAuthManagerTest:test_verifySignatures_RevertWhen_VerifierNotFound() (gas: 27421) -TxAuthManagerTest:test_verifySignatures_RevertWhen_VerifierReturnsFalse() (gas: 32413) -TxAuthManagerTest:test_verifySignatures_SucceedsMultipleSigners() (gas: 42534) -TxAuthManagerTest:test_verifySignatures_SucceedsSingleSigner() (gas: 28188) +TxAuthManagerTest:test_verifySignatures_RevertWhen_AuthModeMismatch() (gas: 25914) +TxAuthManagerTest:test_verifySignatures_RevertWhen_StaticCallFails() (gas: 33239) +TxAuthManagerTest:test_verifySignatures_RevertWhen_TooManySigners() (gas: 212205) +TxAuthManagerTest:test_verifySignatures_RevertWhen_VerifierNotFound() (gas: 29616) +TxAuthManagerTest:test_verifySignatures_RevertWhen_VerifierReturnsFalse() (gas: 34327) +TxAuthManagerTest:test_verifySignatures_SucceedsMultipleSigners() (gas: 45777) +TxAuthManagerTest:test_verifySignatures_SucceedsSingleSigner() (gas: 29980) TxIDUtilsTest:test_computeTxId_ReturnsConsistentId() (gas: 63200) TxIDUtilsTest:test_computeTxId_ReturnsDifferentIdForDifferentCallInfo() (gas: 63666) TxIDUtilsTest:test_computeTxId_ReturnsDifferentIdForDifferentChainId() (gas: 63768) diff --git a/package-lock.json b/package-lock.json index 26f28cb..d12fd86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -187,8 +187,7 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.4.0.tgz", "integrity": "sha512-eCYgWnLg6WO+X52I16TZt8uEjbtdkgLC0SUX/xnAksjjrQI4Xfn4iBRoI5j55dmlOhDv1Y7BoR3cU7e3WWhC6A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@hyperledger-labs/yui-ibc-solidity/node_modules/@openzeppelin/contracts-upgradeable": { "version": "5.4.0", @@ -289,7 +288,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", diff --git a/pkg/testing/cross_test.go b/pkg/testing/cross_test.go index 7e88889..edaee0b 100644 --- a/pkg/testing/cross_test.go +++ b/pkg/testing/cross_test.go @@ -168,7 +168,7 @@ func (suite *CrossTestSuite) TestInitiateTx() { suite.Require().NoError(err) signer1 := crosssimplemodule.AccountData{ - Id: decodeB64("0/syrvWS1CkCswOi9XwXq+gd+dIByQLeH9t/qFrDXqE="), + Id: opts.From.Bytes(), AuthType: crosssimplemodule.AuthTypeData{ Mode: uint8(authtypes.AuthMode_AUTH_MODE_LOCAL), }, diff --git a/src/core/DelegatedLogicHandler.sol b/src/core/DelegatedLogicHandler.sol index f536744..98234bf 100644 --- a/src/core/DelegatedLogicHandler.sol +++ b/src/core/DelegatedLogicHandler.sol @@ -90,7 +90,7 @@ abstract contract DelegatedLogicHandler is TxAuthManagerBase, TxManagerBase, Pac return abi.decode(ret, (TxAuthState.Data)); } - function _verifySignatures(bytes32 txIDHash, Account.Data[] calldata signers) internal virtual override { + function _verifySignatures(bytes32 txIDHash, Account.Data[] memory signers) internal virtual override { _delegateWithData( TX_AUTH_MANAGER, abi.encodeWithSelector(ITxAuthManager.verifySignatures.selector, txIDHash, signers) ); diff --git a/src/core/Initiator.sol b/src/core/Initiator.sol index 68034d9..75cb63d 100644 --- a/src/core/Initiator.sol +++ b/src/core/Initiator.sol @@ -9,7 +9,7 @@ import {ICrossEvent} from "./ICrossEvent.sol"; import {TxIDUtils} from "./TxIDUtils.sol"; import {MsgInitiateTx, MsgInitiateTxResponse, QuerySelfXCCResponse} from "../proto/cross/core/initiator/Initiator.sol"; -import {Account} from "../proto/cross/core/auth/Auth.sol"; +import {Account, AuthType} from "../proto/cross/core/auth/Auth.sol"; import {GoogleProtobufAny} from "@hyperledger-labs/yui-ibc-solidity/contracts/proto/GoogleProtobufAny.sol"; import {ChannelInfo} from "../proto/cross/core/xcc/XCC.sol"; @@ -49,6 +49,8 @@ abstract contract Initiator is IInitiator, TxAuthManagerBase, TxManagerBase, Ree bytes32 txID = msg_.computeTxId(); if (_isTxRecorded(txID)) revert TxIDAlreadyExists(txID); + _validateInitiateSigners(txID, msg_.signers, msg.sender); + // persist as PENDING _createTx(txID, msg_); @@ -98,4 +100,62 @@ abstract contract Initiator is IInitiator, TxAuthManagerBase, TxManagerBase, Ree } return out; } + + function _validateInitiateSigners(bytes32 txID, Account.Data[] calldata signers, address sender) internal { + uint256 len = signers.length; + if (len == 0) { + revert InvalidSignersLength(); + } + + uint256 extensionCount = 0; + bool hasLocalSigner = false; + uint256 localSignerIndex = 0; + + for (uint256 i = 0; i < len; ++i) { + AuthType.AuthMode mode = signers[i].auth_type.mode; + if (mode == AuthType.AuthMode.AUTH_MODE_LOCAL) { + if (hasLocalSigner) { + revert InvalidSignersLength(); + } + hasLocalSigner = true; + localSignerIndex = i; + continue; + } + if (mode == AuthType.AuthMode.AUTH_MODE_EXTENSION) { + ++extensionCount; + continue; + } + revert AuthModeMismatch(); + } + + if (hasLocalSigner) { + _validateLocalSigner(signers[localSignerIndex], sender); + } + + if (extensionCount > 0) { + _verifySignatures(txID, _extractExtensionSigners(signers, extensionCount)); + } + } + + function _validateLocalSigner(Account.Data calldata signer, address sender) internal pure { + bytes memory expectedSignerId = abi.encodePacked(sender); + if (keccak256(signer.id) != keccak256(expectedSignerId)) { + revert SignerMustEqualSender(); + } + } + + function _extractExtensionSigners(Account.Data[] calldata signers, uint256 extensionCount) + internal + pure + returns (Account.Data[] memory extensionSigners) + { + extensionSigners = new Account.Data[](extensionCount); + uint256 index = 0; + for (uint256 i = 0; i < signers.length; ++i) { + if (signers[i].auth_type.mode == AuthType.AuthMode.AUTH_MODE_EXTENSION) { + extensionSigners[index] = signers[i]; + ++index; + } + } + } } diff --git a/src/core/TxAuthManager.sol b/src/core/TxAuthManager.sol index c97f3e1..f2f7377 100644 --- a/src/core/TxAuthManager.sol +++ b/src/core/TxAuthManager.sol @@ -85,7 +85,7 @@ contract TxAuthManager is Initializable, TxAuthManagerBase, CrossStore, ITxAuthM return TxAuthState.Data({remaining_signers: remains}); } - function _verifySignatures(bytes32 txIDHash, Account.Data[] calldata signers) internal virtual override { + function _verifySignatures(bytes32 txIDHash, Account.Data[] memory signers) internal virtual override { uint256 len = signers.length; if (len > MAX_SIGNERS_PER_TX) { @@ -96,13 +96,13 @@ contract TxAuthManager is Initializable, TxAuthManagerBase, CrossStore, ITxAuthM // slither-disable-start calls-loop for (uint256 i = 0; i < len; ++i) { - Account.Data calldata signer = signers[i]; + Account.Data memory signer = signers[i]; if (signer.auth_type.mode != AuthType.AuthMode.AUTH_MODE_EXTENSION) { revert AuthModeMismatch(); } - string calldata typeUrl = signer.auth_type.option.type_url; + string memory typeUrl = signer.auth_type.option.type_url; IAuthExtensionVerifier verifier = s.authVerifiers[typeUrl]; if (address(verifier) == address(0)) { revert VerifierNotFound(typeUrl); @@ -150,11 +150,7 @@ contract TxAuthManager is Initializable, TxAuthManagerBase, CrossStore, ITxAuthM function _accountKey(Account.Data memory a) internal pure returns (bytes32) { if (a.auth_type.mode == AuthType.AuthMode.AUTH_MODE_EXTENSION) { - return keccak256(abi.encode( - a.id, - a.auth_type.mode, - a.auth_type.option.type_url - )); + return keccak256(abi.encode(a.id, a.auth_type.mode, a.auth_type.option.type_url)); } return keccak256(abi.encode(a.id, a.auth_type)); } diff --git a/src/core/TxAuthManagerBase.sol b/src/core/TxAuthManagerBase.sol index dbba8f1..cf87e3b 100644 --- a/src/core/TxAuthManagerBase.sol +++ b/src/core/TxAuthManagerBase.sol @@ -8,5 +8,5 @@ abstract contract TxAuthManagerBase { function _isCompletedAuth(bytes32 txID) internal view virtual returns (bool); function _sign(bytes32 txID, Account.Data[] memory signers) internal virtual returns (bool); function _getAuthState(bytes32 txID) internal view virtual returns (TxAuthState.Data memory); - function _verifySignatures(bytes32 txIDHash, Account.Data[] calldata signers) internal virtual; + function _verifySignatures(bytes32 txIDHash, Account.Data[] memory signers) internal virtual; } diff --git a/test/Authenticator.t.sol b/test/Authenticator.t.sol index 081c353..0674269 100644 --- a/test/Authenticator.t.sol +++ b/test/Authenticator.t.sol @@ -118,7 +118,7 @@ contract MockTxAuthManager is TxAuthManagerBase { function _verifySignatures( bytes32, /*txIDHash*/ - AuthAccount.Data[] calldata + AuthAccount.Data[] memory /*signers*/ ) internal diff --git a/test/Coordinator.t.sol b/test/Coordinator.t.sol index 5e3b20c..4100ea1 100644 --- a/test/Coordinator.t.sol +++ b/test/Coordinator.t.sol @@ -68,7 +68,7 @@ contract MockTxAuthManager is TxAuthManagerBase { TxAuthState.Data memory state; return state; } - function _verifySignatures(bytes32, Account.Data[] calldata) internal virtual override {} + function _verifySignatures(bytes32, Account.Data[] memory) internal virtual override {} function _isCompletedAuth(bytes32 txID) internal view virtual override returns (bool) { return completedAuths[txID]; diff --git a/test/Initiator.t.sol b/test/Initiator.t.sol index eda022f..bec0b60 100644 --- a/test/Initiator.t.sol +++ b/test/Initiator.t.sol @@ -62,7 +62,13 @@ contract MockTxManager is TxManagerBase { contract MockTxAuthManager is TxAuthManagerBase { mapping(bytes32 => bool) public completed; bytes32 public lastInittxID; + bytes32 public lastSigntxID; + bytes32 public lastSignersHash; + uint256 public lastSignersCount; + bytes32 public lastVerifytxID; + uint256 public verifyCount; bool private _signReturns = false; + bool private _verifyShouldRevert = false; function _initAuthState(bytes32 txID, AuthAccount.Data[] memory) internal virtual override { lastInittxID = txID; @@ -73,7 +79,10 @@ contract MockTxAuthManager is TxAuthManagerBase { return completed[txID]; } - function _sign(bytes32 txID, AuthAccount.Data[] memory) internal virtual override returns (bool) { + function _sign(bytes32 txID, AuthAccount.Data[] memory signers) internal virtual override returns (bool) { + lastSigntxID = txID; + lastSignersHash = keccak256(abi.encode(signers)); + lastSignersCount = signers.length; if (_signReturns) { completed[txID] = true; } @@ -84,13 +93,21 @@ contract MockTxAuthManager is TxAuthManagerBase { revert("MockTxAuthManager._getAuthState not implemented"); } - function _verifySignatures(bytes32, AuthAccount.Data[] calldata) internal virtual override { - revert("MockTxAuthManager._verifySignatures not implemented"); + function _verifySignatures(bytes32 txID, AuthAccount.Data[] memory) internal virtual override { + lastVerifytxID = txID; + ++verifyCount; + if (_verifyShouldRevert) { + revert ICrossError.SignatureVerificationFailed(txID); + } } function setSignReturns(bool returnsValue) public { _signReturns = returnsValue; } + + function setVerifyShouldRevert(bool shouldRevert) public { + _verifyShouldRevert = shouldRevert; + } } /** @@ -111,17 +128,32 @@ contract InitiatorTest is Test, ICrossEvent { MsgInitiateTx.Data private baseMsg; AuthAccount.Data private signerA; AuthAccount.Data private signerB; + AuthAccount.Data private senderLocalSigner; + AuthAccount.Data private extSignerA; + AuthAccount.Data private extSignerB; AuthType.Data private localAuthType; + AuthType.Data private extensionAuthType; string private chainIDStr; + function _signersHash(AuthAccount.Data[] memory signers) internal pure returns (bytes32) { + return keccak256(abi.encode(signers)); + } + function setUp() public { harness = new InitiatorHarness(); GoogleProtobufAny.Data memory emptyAny = GoogleProtobufAny.Data({type_url: "", value: ""}); localAuthType = AuthType.Data({mode: AuthType.AuthMode.AUTH_MODE_LOCAL, option: emptyAny}); + extensionAuthType = AuthType.Data({ + mode: AuthType.AuthMode.AUTH_MODE_EXTENSION, + option: GoogleProtobufAny.Data({type_url: "/verifier/test", value: hex"1234"}) + }); signerA = AuthAccount.Data({id: bytes("signerA"), auth_type: localAuthType}); signerB = AuthAccount.Data({id: bytes("signerB"), auth_type: localAuthType}); + senderLocalSigner = AuthAccount.Data({id: abi.encodePacked(address(this)), auth_type: localAuthType}); + extSignerA = AuthAccount.Data({id: bytes("extSignerA"), auth_type: extensionAuthType}); + extSignerB = AuthAccount.Data({id: bytes("extSignerB"), auth_type: extensionAuthType}); chainIDStr = Strings.toString(block.chainid); @@ -129,7 +161,7 @@ contract InitiatorTest is Test, ICrossEvent { ContractTransaction.Data[] memory txs = new ContractTransaction.Data[](1); txs[0].signers = new AuthAccount.Data[](1); - txs[0].signers[0] = signerA; + txs[0].signers[0] = senderLocalSigner; baseMsg = MsgInitiateTx.Data({ chain_id: chainIDStr, @@ -158,6 +190,8 @@ contract InitiatorTest is Test, ICrossEvent { } function test_initiateTx_SucceedsAsPendingWhenSignersNotMet() public { + baseMsg.signers = new AuthAccount.Data[](1); + baseMsg.signers[0] = senderLocalSigner; bytes32 txIDHash = sha256(MsgInitiateTx.encode(baseMsg)); harness.setSignReturns(false); @@ -180,6 +214,8 @@ contract InitiatorTest is Test, ICrossEvent { } function test_initiateTx_SucceedsAsVerifiedWhenSignersMet() public { + baseMsg.signers = new AuthAccount.Data[](1); + baseMsg.signers[0] = senderLocalSigner; bytes32 txIDHash = sha256(MsgInitiateTx.encode(baseMsg)); harness.setSignReturns(true); @@ -198,6 +234,180 @@ contract InitiatorTest is Test, ICrossEvent { assertTrue(harness.completed(txIDHash), "Mock: auth should be completed"); assertEq(harness.createTxCount(), 1, "Mock: _createTx should be called once"); assertEq(harness.lastInittxID(), txIDHash, "Mock: _initAuthState should be called with txID"); + assertEq(harness.lastSigntxID(), txIDHash, "Mock: _sign should be called with txID"); + assertEq(harness.lastSignersCount(), 1, "Mock: _sign should receive one signer"); + assertEq(harness.lastSignersHash(), _signersHash(baseMsg.signers), "Mock: _sign should receive full signer set"); + } + + function test_initiateTx_SucceedsWithExtensionSignerAsPendingWhenAuthNotCompleted() public { + baseMsg.signers = new AuthAccount.Data[](1); + baseMsg.signers[0] = extSignerA; + baseMsg.contract_transactions[0].signers = new AuthAccount.Data[](1); + baseMsg.contract_transactions[0].signers[0] = extSignerA; + bytes32 txIDHash = sha256(MsgInitiateTx.encode(baseMsg)); + + harness.setSignReturns(false); + + vm.expectEmit(true, false, false, true, address(harness)); + emit TxInitiated(abi.encodePacked(txIDHash), address(this), baseMsg); + + MsgInitiateTxResponse.Data memory resp = harness.initiateTx(baseMsg); + + assertEq( + uint256(resp.status), + uint256(MsgInitiateTxResponse.InitiateTxStatus.INITIATE_TX_STATUS_PENDING), + "Status should be PENDING" + ); + assertEq(harness.verifyCount(), 1, "Mock: _verifySignatures should be called once"); + assertEq(harness.lastVerifytxID(), txIDHash, "Mock: _verifySignatures should receive txID"); + } + + function test_initiateTx_SucceedsWithMultipleExtensionSignersAsVerified() public { + baseMsg.signers = new AuthAccount.Data[](2); + baseMsg.signers[0] = extSignerA; + baseMsg.signers[1] = extSignerB; + baseMsg.contract_transactions[0].signers = new AuthAccount.Data[](2); + baseMsg.contract_transactions[0].signers[0] = extSignerA; + baseMsg.contract_transactions[0].signers[1] = extSignerB; + bytes32 txIDHash = sha256(MsgInitiateTx.encode(baseMsg)); + + harness.setSignReturns(true); + + vm.expectEmit(true, false, false, true, address(harness)); + emit TxInitiated(abi.encodePacked(txIDHash), address(this), baseMsg); + + MsgInitiateTxResponse.Data memory resp = harness.initiateTx(baseMsg); + + assertEq( + uint256(resp.status), + uint256(MsgInitiateTxResponse.InitiateTxStatus.INITIATE_TX_STATUS_VERIFIED), + "Status should be VERIFIED" + ); + assertEq(harness.lastSigntxID(), txIDHash, "Mock: _sign should be called with txID"); + assertEq(harness.lastSignersCount(), 2, "Mock: _sign should receive two signers"); + assertEq(harness.lastSignersHash(), _signersHash(baseMsg.signers), "Mock: _sign should receive full signer set"); + assertEq(harness.verifyCount(), 1, "Mock: _verifySignatures should be called once"); + assertEq(harness.lastVerifytxID(), txIDHash, "Mock: _verifySignatures should receive txID"); + } + + function test_initiateTx_SucceedsWithMixedLocalAndExtensionSignersAsVerified() public { + baseMsg.signers = new AuthAccount.Data[](2); + baseMsg.signers[0] = senderLocalSigner; + baseMsg.signers[1] = extSignerA; + baseMsg.contract_transactions[0].signers = new AuthAccount.Data[](2); + baseMsg.contract_transactions[0].signers[0] = senderLocalSigner; + baseMsg.contract_transactions[0].signers[1] = extSignerA; + bytes32 txIDHash = sha256(MsgInitiateTx.encode(baseMsg)); + + harness.setSignReturns(true); + + vm.expectEmit(true, false, false, true, address(harness)); + emit TxInitiated(abi.encodePacked(txIDHash), address(this), baseMsg); + + MsgInitiateTxResponse.Data memory resp = harness.initiateTx(baseMsg); + + assertEq( + uint256(resp.status), + uint256(MsgInitiateTxResponse.InitiateTxStatus.INITIATE_TX_STATUS_VERIFIED), + "Status should be VERIFIED" + ); + assertEq(harness.verifyCount(), 1, "Mock: _verifySignatures should be called once"); + assertEq(harness.lastVerifytxID(), txIDHash, "Mock: _verifySignatures should receive txID"); + } + + function test_initiateTx_SucceedsWithMixedExtensionAndLocalSignersAsPendingWhenAuthNotCompleted() public { + baseMsg.signers = new AuthAccount.Data[](2); + baseMsg.signers[0] = extSignerA; + baseMsg.signers[1] = senderLocalSigner; + baseMsg.contract_transactions[0].signers = new AuthAccount.Data[](2); + baseMsg.contract_transactions[0].signers[0] = extSignerA; + baseMsg.contract_transactions[0].signers[1] = senderLocalSigner; + bytes32 txIDHash = sha256(MsgInitiateTx.encode(baseMsg)); + + harness.setSignReturns(false); + + vm.expectEmit(true, false, false, true, address(harness)); + emit TxInitiated(abi.encodePacked(txIDHash), address(this), baseMsg); + + MsgInitiateTxResponse.Data memory resp = harness.initiateTx(baseMsg); + + assertEq( + uint256(resp.status), + uint256(MsgInitiateTxResponse.InitiateTxStatus.INITIATE_TX_STATUS_PENDING), + "Status should be PENDING" + ); + assertEq(harness.verifyCount(), 1, "Mock: _verifySignatures should be called once"); + assertEq(harness.lastVerifytxID(), txIDHash, "Mock: _verifySignatures should receive txID"); + } + + function test_initiateTx_RevertWhen_SignerLengthZero() public { + baseMsg.signers = new AuthAccount.Data[](0); + + vm.expectRevert(ICrossError.InvalidSignersLength.selector); + harness.initiateTx(baseMsg); + } + + function test_initiateTx_RevertWhen_LocalSignerNotEqualSender() public { + baseMsg.signers = new AuthAccount.Data[](1); + baseMsg.signers[0] = signerA; + + vm.expectRevert(ICrossError.SignerMustEqualSender.selector); + harness.initiateTx(baseMsg); + } + + function test_initiateTx_RevertWhen_SignerAuthModeIsNotLocal() public { + baseMsg.signers = new AuthAccount.Data[](1); + baseMsg.signers[0] = AuthAccount.Data({ + id: abi.encodePacked(address(this)), + auth_type: AuthType.Data({ + mode: AuthType.AuthMode.AUTH_MODE_CHANNEL, option: GoogleProtobufAny.Data("", "") + }) + }); + + vm.expectRevert(ICrossError.AuthModeMismatch.selector); + harness.initiateTx(baseMsg); + } + + function test_initiateTx_RevertWhen_MultipleLocalSignersProvided() public { + baseMsg.signers = new AuthAccount.Data[](2); + baseMsg.signers[0] = senderLocalSigner; + baseMsg.signers[1] = signerB; + + vm.expectRevert(ICrossError.InvalidSignersLength.selector); + harness.initiateTx(baseMsg); + } + + function test_initiateTx_RevertWhen_UnsupportedAuthModeMixedWithLocal() public { + baseMsg.signers = new AuthAccount.Data[](2); + baseMsg.signers[0] = senderLocalSigner; + baseMsg.signers[1] = senderLocalSigner; + baseMsg.signers[1].auth_type = + AuthType.Data({mode: AuthType.AuthMode.AUTH_MODE_CHANNEL, option: GoogleProtobufAny.Data("", "")}); + + vm.expectRevert(ICrossError.AuthModeMismatch.selector); + harness.initiateTx(baseMsg); + } + + function test_initiateTx_RevertWhen_MixedSignersIncludeUnsupportedAuthMode() public { + baseMsg.signers = new AuthAccount.Data[](2); + baseMsg.signers[0] = extSignerA; + baseMsg.signers[1] = senderLocalSigner; + baseMsg.signers[1].auth_type = + AuthType.Data({mode: AuthType.AuthMode.AUTH_MODE_CHANNEL, option: GoogleProtobufAny.Data("", "")}); + + vm.expectRevert(ICrossError.AuthModeMismatch.selector); + harness.initiateTx(baseMsg); + } + + function test_initiateTx_RevertWhen_ExtensionSignatureVerificationFails() public { + baseMsg.signers = new AuthAccount.Data[](1); + baseMsg.signers[0] = extSignerA; + bytes32 txIDHash = sha256(MsgInitiateTx.encode(baseMsg)); + + harness.setVerifyShouldRevert(true); + + vm.expectRevert(abi.encodeWithSelector(ICrossError.SignatureVerificationFailed.selector, txIDHash)); + harness.initiateTx(baseMsg); } function test_initiateTx_RevertWhen_ChainIDMismatch() public {