Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions src/core/Authenticator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

import {IAuthenticator} from "./IAuthenticator.sol";
import {TxAuthManagerBase} from "./TxAuthManagerBase.sol";
import {TxManagerBase} from "./TxManagerBase.sol";

import {
AuthType,
MsgSignTx,
MsgSignTxResponse,
MsgExtSignTx,
MsgExtSignTxResponse,
QueryTxAuthStateRequest,
QueryTxAuthStateResponse,
Account
} from "../proto/cross/core/auth/Auth.sol";
import {GoogleProtobufAny} from "@hyperledger-labs/yui-ibc-solidity/contracts/proto/GoogleProtobufAny.sol";

abstract contract Authenticator is IAuthenticator, TxAuthManagerBase, TxManagerBase {
function signTx(MsgSignTx.Data calldata msg_) external override returns (MsgSignTxResponse.Data memory) {
Account.Data[] memory accounts = _buildLocalAccounts(msg_.signers);

bytes32 txIDHash = sha256(msg_.txID);

bool completed = sign(txIDHash, accounts);
if (completed) {
runTxIfCompleted(txIDHash);
}

emit TxSigned(msg.sender, txIDHash, AuthType.AuthMode.AUTH_MODE_LOCAL);

return MsgSignTxResponse.Data({tx_auth_completed: completed, log: ""});
}

function extSignTx(
MsgExtSignTx.Data calldata /*msg_*/
)
external
override
returns (MsgExtSignTxResponse.Data memory)
{
revert IAuthenticator.ExtSignTxNotImplemented();
}

function txAuthState(
QueryTxAuthStateRequest.Data calldata /*req_*/
)
external
view
override
returns (QueryTxAuthStateResponse.Data memory resp)
{
revert IAuthenticator.TxAuthStateNotImplemented();
}

function _buildLocalAccounts(bytes[] calldata signerIDs) internal pure returns (Account.Data[] memory) {
AuthType.Data memory localAuthType = AuthType.Data({
mode: AuthType.AuthMode.AUTH_MODE_LOCAL, option: GoogleProtobufAny.Data({type_url: "", value: ""})
});

Account.Data[] memory accounts = new Account.Data[](signerIDs.length);
for (uint256 i = 0; i < signerIDs.length; ++i) {
accounts[i] = Account.Data({id: signerIDs[i], auth_type: localAuthType});
}
return accounts;
}
}
2 changes: 2 additions & 0 deletions src/core/CrossModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import "./PacketHandler.sol";
import "./IBCKeeper.sol";

import {Initiator} from "./Initiator.sol";
import {Authenticator} from "./Authenticator.sol";
import {TxAuthManager} from "./TxAuthManager.sol";
import {TxManager} from "./TxManager.sol";
import {TxRunner} from "./TxRunner.sol";
Expand All @@ -24,6 +25,7 @@ abstract contract CrossModule is
IBCKeeper,
PacketHandler,
Initiator,
Authenticator,
TxAuthManager,
TxManager,
TxRunner
Expand Down
5 changes: 2 additions & 3 deletions src/core/IAuthenticator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import {
} from "../proto/cross/core/auth/Auth.sol";

interface IAuthenticator {
error AuthStateAlreadyInitialized(bytes32 txID);
error IDNotFound(bytes32 txID);
error AuthAlreadyCompleted(bytes32 txID);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed duplicate error definitions (already defined in TxAuthManagerBase).

error ExtSignTxNotImplemented();
error TxAuthStateNotImplemented();

event TxSigned(address indexed signer, bytes32 indexed txID, AuthType.AuthMode method);

Expand Down
11 changes: 6 additions & 5 deletions src/proto/cross/core/tx/Tx.sol
Original file line number Diff line number Diff line change
Expand Up @@ -613,11 +613,12 @@ library ResolvedContractTransaction {
* @param counters The counters for repeated fields
* @return The number of bytes decoded
*/
function _read_unpacked_repeated_call_results(uint256 p, bytes memory bs, Data memory r, uint256[6] memory counters)
internal
pure
returns (uint256)
{
function _read_unpacked_repeated_call_results(
uint256 p,
bytes memory bs,
Data memory r,
uint256[6] memory counters
) internal pure returns (uint256) {
/**
* if `r` is NULL, then only counting the number of fields.
*/
Expand Down
202 changes: 202 additions & 0 deletions test/Authenticator.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: Apache-2.0
// solhint-disable one-contract-per-file, func-name-mixedcase, var-name-mixedcase, gas-small-strings
pragma solidity ^0.8.20;

import "forge-std/src/Test.sol";
import "../src/core/Authenticator.sol";
import {TxManagerBase} from "../src/core/TxManagerBase.sol";
import {TxAuthManagerBase} from "../src/core/TxAuthManagerBase.sol";

import {MsgInitiateTx} from "../src/proto/cross/core/initiator/Initiator.sol";
import {IAuthenticator} from "../src/core/IAuthenticator.sol";
import {
Account as AuthAccount,
AuthType,
TxAuthState,
MsgSignTx,
MsgSignTxResponse,
MsgExtSignTx,
QueryTxAuthStateRequest
} from "../src/proto/cross/core/auth/Auth.sol";
import {IbcCoreClientV1Height} from "../src/proto/ibc/core/client/v1/client.sol";

contract MockTxManager is TxManagerBase {
bytes32 public lastRunTxID;
uint256 public runTxCount;

function createTx(
bytes32,
/*txID*/
MsgInitiateTx.Data calldata /*src*/
)
internal
virtual
override
{}

function runTxIfCompleted(bytes32 txID) internal virtual override {
lastRunTxID = txID;
++runTxCount;
}

function isTxRecorded(
bytes32 /*txID*/
)
internal
view
virtual
override
returns (bool)
{
return false;
}
}

contract MockTxAuthManager is TxAuthManagerBase {
mapping(bytes32 => bool) public completed;
bool private _signReturns = false;

function initAuthState(
bytes32,
/*txID*/
Account.Data[] memory /*signers*/
)
internal
virtual
override
{}

function isCompletedAuth(
bytes32 /*txID*/
)
internal
view
virtual
override
returns (bool)
{
return false;
}

function sign(
bytes32 txID,
Account.Data[] memory /*signers*/
)
internal
virtual
override
returns (bool)
{
if (_signReturns) {
completed[txID] = true;
}
return _signReturns;
}
function getAuthState(bytes32) internal view virtual override returns (TxAuthState.Data memory) {}

function setSignReturns(bool returnsValue) public {
_signReturns = returnsValue;
}
}

contract AuthenticatorHarness is Authenticator, MockTxAuthManager, MockTxManager {
function exposed_buildLocalAccounts(bytes[] calldata signerIDs) public pure returns (AuthAccount.Data[] memory) {
return _buildLocalAccounts(signerIDs);
}
}

contract AuthenticatorTest is Test {
AuthenticatorHarness private harness;
MsgSignTx.Data private baseMsg;
bytes32 private txIDHash;
bytes private signerABytes = bytes("signerA");
bytes private signerBBytes = bytes("signerB");

event TxSigned(address indexed signer, bytes32 indexed txID, AuthType.AuthMode method);

function setUp() public {
harness = new AuthenticatorHarness();

bytes[] memory signers = new bytes[](1);
signers[0] = signerABytes;

baseMsg = MsgSignTx.Data({
txID: bytes("test-tx-id"),
signers: signers,
timeout_height: IbcCoreClientV1Height.Data(0, 0),
timeout_timestamp: 0
});

txIDHash = sha256(baseMsg.txID);
}

function test_signTx_SucceedsAsPending() public {
harness.setSignReturns(false);

MsgSignTxResponse.Data memory resp = harness.signTx(baseMsg);
assertFalse(resp.tx_auth_completed, "response should indicate not completed");
assertEq(resp.log, "", "log should be empty");
assertFalse(harness.completed(txIDHash), "Mock: auth should not be completed");
assertEq(harness.runTxCount(), 0, "Mock: runTxIfCompleted should not be called");
}

function test_signTx_SucceedsAsCompletedAndEmitsEvent() public {
harness.setSignReturns(true);

vm.expectEmit(true, true, false, true, address(harness));
emit TxSigned(address(this), txIDHash, AuthType.AuthMode.AUTH_MODE_LOCAL);

MsgSignTxResponse.Data memory resp = harness.signTx(baseMsg);

assertTrue(resp.tx_auth_completed, "response should indicate completed");
assertEq(resp.log, "", "log should be empty");
assertTrue(harness.completed(txIDHash), "Mock: auth should be completed");
assertEq(harness.runTxCount(), 1, "Mock: runTxIfCompleted should be called once");
assertEq(harness.lastRunTxID(), txIDHash, "Mock: runTxIfCompleted called with correct txID");
}

function test_extSignTx_RevertsNotImplemented() public {
MsgExtSignTx.Data memory msg_;
msg_.txID = bytes("ext-tx");
msg_.signers = new AuthAccount.Data[](0);

vm.expectRevert(IAuthenticator.ExtSignTxNotImplemented.selector);
harness.extSignTx(msg_);
}

function test_txAuthState_RevertsNotImplemented() public {
QueryTxAuthStateRequest.Data memory req;
req.txID = bytes("query-tx");

vm.expectRevert(IAuthenticator.TxAuthStateNotImplemented.selector);
harness.txAuthState(req);
}

function test_buildLocalAccounts_BuildsCorrectly() public {
bytes[] memory signerIDs = new bytes[](2);
signerIDs[0] = signerABytes;
signerIDs[1] = signerBBytes;

AuthAccount.Data[] memory accounts = harness.exposed_buildLocalAccounts(signerIDs);

assertEq(accounts.length, 2, "Array length mismatch");

assertEq(accounts[0].id, signerABytes, "Signer A ID mismatch");
assertEq(
uint256(accounts[0].auth_type.mode),
uint256(AuthType.AuthMode.AUTH_MODE_LOCAL),
"Signer A auth type mismatch"
);
assertEq(accounts[0].auth_type.option.type_url, "", "Signer A option type_url should be empty");
assertEq(accounts[0].auth_type.option.value, "", "Signer A option value should be empty");

assertEq(accounts[1].id, signerBBytes, "Signer B ID mismatch");
assertEq(
uint256(accounts[1].auth_type.mode),
uint256(AuthType.AuthMode.AUTH_MODE_LOCAL),
"Signer B auth type mismatch"
);
assertEq(accounts[1].auth_type.option.type_url, "", "Signer B option type_url should be empty");
assertEq(accounts[1].auth_type.option.value, "", "Signer B option value should be empty");
}
}