Skip to content

Instantly share code, notes, and snippets.

@romeroadrian
Created May 26, 2023 17:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save romeroadrian/535a969c96e0a6f78781287bd0931b6a to your computer and use it in GitHub Desktop.
Save romeroadrian/535a969c96e0a6f78781287bd0931b6a to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/libs/Bytes.sol";
import "../src/libs/SignatureValidator.sol";
import "../src/AmbireAccountFactory.sol";
import "../src/AmbireAccount.sol";
import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";
interface ACoolWalletFeature {
function doSomethingGreatInWallet(uint256) external;
}
contract TestTryCatch {
uint256[20] public foo;
function test() external {
// simulate expensive operation
for (uint256 index = 0; index < 20; index++) {
foo[index] = index + 1;
}
}
}
contract AuditTest is Test {
event LogErr(address indexed to, uint256 value, bytes data, bytes returnData);
function setUp() public {
}
function test_AmbireAccountFactory_EmptyBytecode() public {
AmbireAccountFactory factory = new AmbireAccountFactory(address(0));
bytes memory code = hex"00";
uint256 salt = 42;
address expectedAddr = address(
uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(factory), salt, keccak256(code)))))
);
factory.deploy(code, salt);
// deploy succeeded but account has no code
assertEq(expectedAddr.code.length, 0);
// this means that a new call to deploy will fail, as the account already exists
// but deploySafe will think otherwise because codesize is zero
vm.expectRevert();
factory.deploy(code, salt);
}
function test_AmbireAccount_FallbackDoesntRevert() public {
address user = makeAddr("user");
address[] memory addrs = new address[](1);
addrs[0] = user;
AmbireAccount account = new AmbireAccount(addrs);
// wallet doesn't have fallback
assertEq(account.privileges(address(0x6969)), bytes32(0));
// the following doesnt fail
ACoolWalletFeature(address(account)).doSomethingGreatInWallet(42);
}
function test_AmbireAccount_LowLevelCallsToAccountsWithNoCode() public {
address user = makeAddr("user");
address[] memory addrs = new address[](1);
addrs[0] = user;
AmbireAccount account = new AmbireAccount(addrs);
AmbireAccount.Transaction[] memory txns = new AmbireAccount.Transaction[](1);
txns[0].to = address(0x42);
txns[0].value = 0;
txns[0].data = abi.encodeWithSignature("someFunction(address,uint256)", user, 42);
// destination doesnt have any code
assertEq(txns[0].to.code.length, 0);
// transaction succeeds
vm.prank(user);
account.executeBySender(txns);
}
function test_AmbireAccount_ForceFailTryCatch() public {
address user = makeAddr("user");
address[] memory addrs = new address[](1);
addrs[0] = user;
AmbireAccount account = new AmbireAccount(addrs);
TestTryCatch testTryCatch = new TestTryCatch();
AmbireAccount.Transaction[] memory txns = new AmbireAccount.Transaction[](1);
txns[0].to = address(account);
txns[0].value = 0;
txns[0].data = abi.encodeWithSelector(
AmbireAccount.tryCatch.selector,
address(testTryCatch),
uint256(0),
abi.encodeWithSelector(TestTryCatch.test.selector)
);
// This should actually be a call to "execute", we simplify the case using "executeBySender"
// to avoid the complexity of providing a signature. Core issue remains the same.
vm.expectEmit(true, false, false, false);
emit LogErr(address(testTryCatch), 0, "", "");
vm.prank(user);
account.executeBySender{gas: 450_000}(txns);
// assert call to TestTryCatch failed
assertEq(testTryCatch.foo(0), 0);
}
// NOTE: moved this test to a separate file in order to properly test selfdestruct
// function test_AmbireAccount_DestroyImplementation() public {
// // Master account implementation can be destroyed by any of the configured privileges
// address deployer = makeAddr("deployer");
// address user = makeAddr("user");
// // Lets say deployer creates reference implementation
// address[] memory addrsImpl = new address[](1);
// addrsImpl[0] = deployer;
// AmbireAccount implementation = new AmbireAccount(addrsImpl);
// // User deploys wallet
// address[] memory addrsWallet = new address[](1);
// addrsWallet[0] = user;
// AmbireAccount wallet = AmbireAccount(payable(
// new AccountProxy(addrsWallet, address(implementation))
// ));
// // Test the wallet is working ok
// assertTrue(wallet.supportsInterface(0x4e2312e0));
// // Now privilege sets fallback
// Destroyer destroyer = new Destroyer();
// AmbireAccount.Transaction[] memory txns = new AmbireAccount.Transaction[](1);
// txns[0].to = address(implementation);
// txns[0].value = 0;
// txns[0].data = abi.encodeWithSelector(
// AmbireAccount.setAddrPrivilege.selector,
// address(0x6969),
// bytes32(uint256(uint160(address(destroyer))))
// );
// vm.prank(deployer);
// implementation.executeBySender(txns);
// // and destroys master implementation
// Destroyer(address(implementation)).destruct();
// console.log(address(implementation).code.length);
// // Now every wallet (proxy) that points to this master implementation will be bricked
// assertTrue(wallet.supportsInterface(0x4e2312e0));
// }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment