-
Notifications
You must be signed in to change notification settings - Fork 311
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: add NoDelegateCall modifiers to DualVMToken
- Loading branch information
Showing
7 changed files
with
335 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.27; | ||
|
||
contract DualVmTokenHack { | ||
address target; | ||
uint256 constant AMOUNT = 1337; | ||
|
||
constructor(address _target) { | ||
target = _target; | ||
} | ||
|
||
function tryApproveEvm() external returns (bool success) { | ||
(success,) = target.delegatecall( | ||
abi.encodeWithSelector(bytes4(keccak256("approve(address,uint256)")), address(this), AMOUNT) | ||
); | ||
} | ||
|
||
function tryApproveStarknet() external returns (bool success) { | ||
(success,) = target.delegatecall( | ||
abi.encodeWithSelector( | ||
bytes4(keccak256("approve(uint256,uint256)")), uint256(uint160(address(this))), AMOUNT | ||
) | ||
); | ||
} | ||
|
||
function tryTransferEvm() external returns (bool success) { | ||
(success,) = target.delegatecall( | ||
abi.encodeWithSelector(bytes4(keccak256("transfer(address,uint256)")), address(this), AMOUNT) | ||
); | ||
} | ||
|
||
function tryTransferStarknet() external returns (bool success) { | ||
(success,) = target.delegatecall( | ||
abi.encodeWithSelector( | ||
bytes4(keccak256("transfer(uint256,uint256)")), uint256(uint160(address(this))), AMOUNT | ||
) | ||
); | ||
} | ||
|
||
function tryTransferFromEvmEvm() external returns (bool success) { | ||
(success,) = target.delegatecall( | ||
abi.encodeWithSelector( | ||
bytes4(keccak256("transferFrom(address,address,uint256)")), msg.sender, address(this), AMOUNT | ||
) | ||
); | ||
} | ||
|
||
function tryTransferFromStarknetEvm() external returns (bool success) { | ||
(success,) = target.delegatecall( | ||
abi.encodeWithSelector( | ||
bytes4(keccak256("transferFrom(uint256,address,uint256)")), | ||
uint256(uint160(msg.sender)), | ||
address(this), | ||
AMOUNT | ||
) | ||
); | ||
} | ||
|
||
function tryTransferFromEvmStarknet() external returns (bool success) { | ||
(success,) = target.delegatecall( | ||
abi.encodeWithSelector( | ||
bytes4(keccak256("transferFrom(address,uint256,uint256)")), | ||
msg.sender, | ||
uint256(uint160(address(this))), | ||
AMOUNT | ||
) | ||
); | ||
} | ||
|
||
function tryTransferFromStarknetStarknet() external returns (bool success) { | ||
(success,) = target.delegatecall( | ||
abi.encodeWithSelector( | ||
bytes4(keccak256("transferFrom(uint256,uint256,uint256)")), | ||
uint256(uint160(msg.sender)), | ||
uint256(uint160(address(this))), | ||
AMOUNT | ||
) | ||
); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
solidity_contracts/src/Security/L2KakarotMessagingHack.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.27; | ||
|
||
import {L2KakarotMessaging} from "../L1L2Messaging/L2KakarotMessaging.sol"; | ||
|
||
contract L2MessagingHack { | ||
L2KakarotMessaging public immutable target; | ||
|
||
constructor(address _target) { | ||
target = L2KakarotMessaging(_target); | ||
} | ||
|
||
function trySendMessageToL1(address to, bytes calldata data) external returns (bool success) { | ||
// Try to send message through delegatecall | ||
(success,) = | ||
address(target).delegatecall(abi.encodeWithSelector(L2KakarotMessaging.sendMessageToL1.selector, to, data)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
// Author: Uniswap Labs https://github.com/Uniswap/v3-core/blob/main/contracts/NoDelegateCall.sol | ||
pragma solidity 0.8.27; | ||
|
||
/// @title Prevents delegatecall to a contract | ||
/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract | ||
abstract contract NoDelegateCall { | ||
/// @dev The original address of this contract | ||
address private immutable original; | ||
|
||
constructor() { | ||
// Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode. | ||
// In other words, this variable won't change when it's checked at runtime. | ||
original = address(this); | ||
} | ||
|
||
/// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method, | ||
/// and the use of immutable means the address bytes are copied in every place the modifier is used. | ||
function checkNotDelegateCall() private view { | ||
require(address(this) == original); | ||
} | ||
|
||
/// @notice Prevents delegatecall into the modified method | ||
modifier noDelegateCall() { | ||
checkNotDelegateCall(); | ||
_; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import logging | ||
|
||
import pytest | ||
import pytest_asyncio | ||
|
||
from kakarot_scripts.utils.kakarot import deploy as deploy_kakarot | ||
from kakarot_scripts.utils.starknet import deploy as deploy_starknet | ||
from kakarot_scripts.utils.starknet import get_contract as get_contract_starknet | ||
from kakarot_scripts.utils.starknet import invoke | ||
|
||
logging.basicConfig() | ||
logger = logging.getLogger(__name__) | ||
logger.setLevel(logging.INFO) | ||
|
||
|
||
@pytest_asyncio.fixture(scope="function") | ||
async def starknet_token(owner): | ||
address = await deploy_starknet( | ||
"StarknetToken", int(1e18), owner.starknet_contract.address | ||
) | ||
return get_contract_starknet("StarknetToken", address=address) | ||
|
||
|
||
@pytest_asyncio.fixture(scope="function") | ||
async def dual_vm_token(kakarot, starknet_token, owner): | ||
dual_vm_token = await deploy_kakarot( | ||
"CairoPrecompiles", | ||
"DualVmToken", | ||
kakarot.address, | ||
starknet_token.address, | ||
caller_eoa=owner.starknet_contract, | ||
) | ||
|
||
await invoke( | ||
"kakarot", | ||
"set_authorized_cairo_precompile_caller", | ||
int(dual_vm_token.address, 16), | ||
True, | ||
) | ||
return dual_vm_token | ||
|
||
|
||
@pytest_asyncio.fixture(scope="function") | ||
async def hack_vm_token(dual_vm_token, owner): | ||
hack_vm_token = await deploy_kakarot( | ||
"CairoPrecompiles", | ||
"DualVmTokenHack", | ||
dual_vm_token.address, | ||
caller_eoa=owner.starknet_contract, | ||
) | ||
return hack_vm_token | ||
|
||
|
||
@pytest.mark.asyncio(scope="module") | ||
@pytest.mark.CairoPrecompiles | ||
class TestDualVmToken: | ||
class TestActions: | ||
async def test_malicious_approve_address_should_fail_nodelegatecaltest_malicious_approve_address_should_fail_nodelegatecall( | ||
self, dual_vm_token, hack_vm_token, owner | ||
): | ||
result = await hack_vm_token.functions["tryApproveEvm()"]() | ||
call_succeeded = int.from_bytes(bytes(result["response"]), "big") | ||
assert call_succeeded == 0 | ||
|
||
allowance = await dual_vm_token.functions["allowance(address,address)"]( | ||
owner.address, hack_vm_token.address | ||
) | ||
assert allowance == 0 | ||
|
||
async def test_malicious_approve_starknet_should_fail_nodelegatecall( | ||
self, dual_vm_token, hack_vm_token, owner | ||
): | ||
result = await hack_vm_token.functions["tryApproveStarknet()"]() | ||
call_succeeded = int.from_bytes(bytes(result["response"]), "big") | ||
assert call_succeeded == 0 | ||
|
||
allowance = await dual_vm_token.functions["allowance(address,uint256)"]( | ||
owner.address, int(hack_vm_token.address, 16) | ||
) | ||
assert allowance == 0 | ||
|
||
async def test_malicious_transfer_address_should_fail_nodelegatecall( | ||
self, dual_vm_token, hack_vm_token, owner | ||
): | ||
result = await hack_vm_token.functions["tryTransferEvm()"]() | ||
call_succeeded = int.from_bytes(bytes(result["response"]), "big") | ||
assert call_succeeded == 0 | ||
|
||
balance = await dual_vm_token.functions["balanceOf(address)"]( | ||
hack_vm_token.address | ||
) | ||
assert balance == 0 | ||
|
||
async def test_malicious_transfer_starknet_should_fail_nodelegatecall( | ||
self, dual_vm_token, hack_vm_token, owner | ||
): | ||
result = await hack_vm_token.functions["tryTransferStarknet()"]() | ||
call_succeeded = int.from_bytes(bytes(result["response"]), "big") | ||
assert call_succeeded == 0 | ||
|
||
balance = await dual_vm_token.functions["balanceOf(uint256)"]( | ||
int(hack_vm_token.address, 16) | ||
) | ||
assert balance == 0 | ||
|
||
async def test_malicious_transfer_from_address_address_should_fail_nodelegatecall( | ||
self, dual_vm_token, hack_vm_token, owner | ||
): | ||
result = await hack_vm_token.functions["tryTransferFromEvmEvm()"]() | ||
call_succeeded = int.from_bytes(bytes(result["response"]), "big") | ||
assert call_succeeded == 0 | ||
|
||
balance = await dual_vm_token.functions["balanceOf(address)"]( | ||
hack_vm_token.address | ||
) | ||
assert balance == 0 | ||
|
||
async def test_malicious_transfer_from_starknet_address_should_fail_nodelegatecall( | ||
self, dual_vm_token, hack_vm_token, owner | ||
): | ||
result = await hack_vm_token.functions["tryTransferFromStarknetEvm()"]() | ||
call_succeeded = int.from_bytes(bytes(result["response"]), "big") | ||
assert call_succeeded == 0 | ||
|
||
balance = await dual_vm_token.functions["balanceOf(address)"]( | ||
hack_vm_token.address | ||
) | ||
assert balance == 0 | ||
|
||
async def test_malicious_transfer_from_address_starknet_should_fail_nodelegatecall( | ||
self, dual_vm_token, hack_vm_token, owner | ||
): | ||
result = await hack_vm_token.functions["tryTransferFromEvmStarknet()"]() | ||
call_succeeded = int.from_bytes(bytes(result["response"]), "big") | ||
assert call_succeeded == 0 | ||
|
||
balance = await dual_vm_token.functions["balanceOf(uint256)"]( | ||
int(hack_vm_token.address, 16) | ||
) | ||
assert balance == 0 | ||
|
||
async def test_malicious_transfer_from_starknet_starknet_should_fail_nodelegatecall( | ||
self, dual_vm_token, hack_vm_token, owner | ||
): | ||
result = await hack_vm_token.functions[ | ||
"tryTransferFromStarknetStarknet()" | ||
]() | ||
call_succeeded = int.from_bytes(bytes(result["response"]), "big") | ||
assert call_succeeded == 0 | ||
|
||
balance = await dual_vm_token.functions["balanceOf(uint256)"]( | ||
int(hack_vm_token.address, 16) | ||
) | ||
assert balance == 0 |
Oops, something went wrong.