solidity delegatecall prevention doesn't works - ethereum

I am a newbie studying Uniswap V3's github code. I came up with noDelegateCall.sol and I found out the way to prevent a contract to be delegatecalled from another cotract. As I tried to implement this, I caught up with the problem.
pragma solidity >0.8.0;
contract Receiver {
string greeting = "Hello";
address private immutable original;
event Greeting(string greeting, address original, address addressThis);
constructor() {
original = address(this);
}
function checkNotDelegateCall() private view {
require(address(this) == original);
}
modifier noDelegateCall() {
checkNotDelegateCall();
_;
}
function greet() external noDelegateCall {
emit Greeting(greeting, original, address(this));
}
}
contract Sender {
string greeting = "Hi";
function delegatedGreeting(address _contract) external {
(bool success,) = _contract.delegatecall(
abi.encodeWithSignature("greet()")
);
}
}
If I call function delegatedGreeting, I expect the function to be reverted because variables original and address(this) differs. However, although it emits an empty event, it still doesn't reverts. Why does this happen?

When the low-level delegatecall reverts, it doesn't automatically revert the main transaction. Instead, it returns false as the first return value of the delegatecall() function.
So you need to check the return value (in your case bool success) and validate that.
function delegatedGreeting(address _contract) external {
(bool success,) = _contract.delegatecall(
abi.encodeWithSignature("greet()")
);
require(success == true, "delegatecall failed");
}

Related

Ethernaut level 24 - Puzzle Wallet: to which contract the wallet object on the browser console refers to?

I got a little confused when trying to solve this level and I got even more confused when I read this solution.
I thought that the contract object loaded in the browser console was the PuzzleWallet contract, because when I look at its ABI, there are all the functions from that contract and none from the PuzzleProxy. And the PuzzleWallet does not inherit from any other contract. I don't understand how it is possible to call proposeNewAdmin() function from the PuzzleProxy contract, if it does not inherit from PuzzleProxy...
On the other hand, if the contract object in the browser console is the PuzzleProxy, why there are all the functions from the PuzzleWallet in the ABI and none from the PuzzleProxy?
Here is the Ethernaut level.
The contracts are:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "#openzeppelin/contracts/math/SafeMath.sol";
import "#openzeppelin/contracts/proxy/UpgradeableProxy.sol";
contract PuzzleProxy is UpgradeableProxy {
address public pendingAdmin;
address public admin;
constructor(address _admin, address _implementation, bytes memory _initData) UpgradeableProxy(_implementation, _initData) public {
admin = _admin;
}
modifier onlyAdmin {
require(msg.sender == admin, "Caller is not the admin");
_;
}
function proposeNewAdmin(address _newAdmin) external {
pendingAdmin = _newAdmin;
}
function approveNewAdmin(address _expectedAdmin) external onlyAdmin {
require(pendingAdmin == _expectedAdmin, "Expected new admin by the current admin is not the pending admin");
admin = pendingAdmin;
}
function upgradeTo(address _newImplementation) external onlyAdmin {
_upgradeTo(_newImplementation);
}
}
contract PuzzleWallet {
using SafeMath for uint256;
address public owner;
uint256 public maxBalance;
mapping(address => bool) public whitelisted;
mapping(address => uint256) public balances;
function init(uint256 _maxBalance) public {
require(maxBalance == 0, "Already initialized");
maxBalance = _maxBalance;
owner = msg.sender;
}
modifier onlyWhitelisted {
require(whitelisted[msg.sender], "Not whitelisted");
_;
}
function setMaxBalance(uint256 _maxBalance) external onlyWhitelisted {
require(address(this).balance == 0, "Contract balance is not 0");
maxBalance = _maxBalance;
}
function addToWhitelist(address addr) external {
require(msg.sender == owner, "Not the owner");
whitelisted[addr] = true;
}
function deposit() external payable onlyWhitelisted {
require(address(this).balance <= maxBalance, "Max balance reached");
balances[msg.sender] = balances[msg.sender].add(msg.value);
}
function execute(address to, uint256 value, bytes calldata data) external payable onlyWhitelisted {
require(balances[msg.sender] >= value, "Insufficient balance");
balances[msg.sender] = balances[msg.sender].sub(value);
(bool success, ) = to.call{ value: value }(data);
require(success, "Execution failed");
}
function multicall(bytes[] calldata data) external payable onlyWhitelisted {
bool depositCalled = false;
for (uint256 i = 0; i < data.length; i++) {
bytes memory _data = data[i];
bytes4 selector;
assembly {
selector := mload(add(_data, 32))
}
if (selector == this.deposit.selector) {
require(!depositCalled, "Deposit can only be called once");
// Protect against reusing msg.value
depositCalled = true;
}
(bool success, ) = address(this).delegatecall(data[i]);
require(success, "Error while delegating call");
}
}
}
The contract.abi object on the browser console is:
I understand the concept of proxy patterns. But I thought that it would be done via delegatecall() functions. For example, the addToWhiteList() function on the PuzzleWallet contract would be called by a function as follows on the PuzzleProxy contract:
function addToWhitelist(address _add) external {
puzzleWalletAddress.delegatecall(abi.encodeWithSignature("addToWhitelist(address)", _add);)
}
Hopefully my question here is not as confusing as I got while trying to solve this level :)
Appreciate very much if anyone coould help me! Thanks!
I also got confused by the same thing :)
The answer is that they created the Web3 contract object with the ABI of the logic contract but with the address of the proxy contract so you can interact with the logic as if there wasn't a proxy pattern under the hood.
In reality it is calling the proxy contract with the data of a function of the logic contract. As the function doesn't exist in the proxy, its fallback function runs and redirects the call to the logic contract via delegatecall.
So if you want to call proposeNewAdmin() in the proxy, call the contract mounted in the console (aka the proxy contract) but instead of using any function from the ABI defined there (which is the logic abi), make a generic transaction calling proposeNewAdmin(). As the function does exist in the proxy, it won't trigger the fallback.
web3.eth.abi.encodeFunctionSignature("proposeNewAdmin(address)");
> '0xa6376746'
web3.eth.abi.encodeParameter("address", player);
> '0x000000000000000000000000c3a005e15cb35689380d9c1318e981bca9339942'
contract.sendTransaction({ data: '0xa6376746000000000000000000000000c3a005e15cb35689380d9c1318e981bca9339942' });

Delegatecall: function doesn't change a parentContract array element

I deployed two contracts (A,B). Function A.delegateUpdateIDX[contract_B,1] triggers function B.updateIDX[1]
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.4;
contract A {
string[] private arr = ['a','b','c'];
function showIDX(uint _IDX) public view returns(string memory)
{
return arr[_IDX];
}
function delegateUpdateIDX(address _contractB, uint _IDX) external returns(bool _success)
{
(bool b,)=_contractB.delegatecall( abi.encodeWithSignature("updateIDX(uint _IDX)", _IDX) );
return b;
}
}
//###################################
contract B {
string[] private arr;
function updateIDX(uint _IDX) external
{
arr[_IDX]='X';
}
}
Then I call functions (in Remix):
A.showIDX[1]
returns 'b'
A.delegateUpdateIDX(B.address,1)
no errors
(should update A.storage and set A.arr[1] to 'X')
but the delegateCall() returns 'success=false' for some reason
and A.arr[1] doesn't change to 'X'
A.showIDX[1]
returns 'b' again (not 'X' as expected)
So, basically, I have no idea why delegateCall() returns 'success=false' and therefore why doesn't A.arr[1] change from 'b' to 'X'. What am I doing wrong?
Managed to make it work
abi.encodeWithSignature("updateIDX(uint _IDX)", _IDX) shouldn't have '_IDX' name in it, so I changed it to abi.encodeWithSignature("updateIDX(uint)", _IDX)
-- it still didn't work, the delegateCall() kept returning 'false' --
changed 'uint' to 'uint256' in both encodeWithSignature() and B.updateIDX()
worked fine this time

Not able to compare strings on Mist

I have created a contract where I am taking 2 Hashes from the user and trying to compare them both which in return would give a boolean value of true or false. It works good on remix but when I try to run the contract on Mist the compareString function just shows a message "NO". here is my code.
pragma solidity ^0.4.18;
contract Hash {
string fhash;
string comphash;
event Instructor(string _fhash);
event Instructors(string _comphash);
function setinstructor(string _fhash) public {
fhash = _fhash;
emit Instructor(_fhash);
}
function getinstructor() public constant returns(string){
return(fhash);
}
function setinstructors(string _comphash) public {
comphash = _comphash;
emit Instructors(_comphash);
}
function getinstructors() public constant returns(string){
return(comphash);
}
function compareStrings() public view returns (bool){
return sha256(fhash) == sha256(comphash)? true : false;
}
}
Image of Mist response

Interact between smart contracts does NOT work

I deployed two contracts, one is Callee and the other is Caller. Caller consumes functions provided by Callee. Function call directly to Callee is success, however, Caller does NOT work. Actually, I'v tried different cases from internet, none of them works. Do I miss some tricky things? Below is source code:
Callee.sol
pragma solidity ^0.4.6;
contract Callee {
uint[] public values;
function getValue(uint initial) public pure returns(uint) {
return initial + 150;
}
function storeValue(uint value) public {
values.push(value);
}
function getValues() public view returns(uint) {
return values.length;
}
}
Caller.sol
pragma solidity ^0.4.6;
contract Caller {
function someAction(address addr) public returns(uint) {
Callee c = Callee(addr);
return c.getValue(100);
}
function storeAction(address addr) public returns(uint) {
Callee c = Callee(addr);
c.storeValue(100);
return c.getValues();
}
function someUnsafeAction(address addr) public returns(bool){
return addr.call(bytes4(keccak256("storeValue(uint256)")), 100);
}
}
contract Callee {
function getValue(uint initialValue) public returns(uint);
function storeValue(uint value) public;
function getValues() public returns(uint);
}
Caller is technically working, but not doing what you're expecting.
The problem is that Caller.someAction is not marked with the pure or view modifiers, so your function call is triggering a new transaction. Transaction calls can't return values (it compiles and runs, but nothing is returned).
If you change Caller.someAction to be a pure function (and, also change Callee.getValue in the interface to be pure as well to match the contract), you will get the expected 250 returned.
pragma solidity ^0.4.6;
contract Caller {
function someAction(address addr) public pure returns(uint) {
Callee c = Callee(addr);
return c.getValue(100);
}
function storeAction(address addr) public returns(uint) {
Callee c = Callee(addr);
c.storeValue(100);
return c.getValues();
}
function someUnsafeAction(address addr) public returns(bool){
return addr.call(bytes4(keccak256("storeValue(uint256)")), 100);
}
}
contract Callee {
function getValue(uint initialValue) public pure returns(uint);
function storeValue(uint value) public;
function getValues() public returns(uint);
}
In terminal:
$ truffle migrate --reset
Compiling .\contracts\Callee.sol...
Compiling .\contracts\SimpleContract.sol...
Writing artifacts to .\build\contracts
Using network 'development'.
Running migration: 1_initial_migration.js
Replacing Migrations...
... 0x58a14c93acc733bb08e4bb56978d0bb466f8aca7659673426d989ee7e0e626f3
Migrations: 0x69ed5e4d6172639ed7d3c456ea8b2f2562c7dbcd
Saving successful migration to network...
... 0x9a3831076748cc6179ef3e4b3e466c3a4a871e196376bc22c984e91e95fdc567
Saving artifacts...
Running migration: 2_deploy_contracts.js
Replacing Caller...
... 0x7be6840c86a356decacc13967d50ef4fea30e86d30d69b19fb9998ce500c95e8
Caller: 0xb8c5e079af71813acac73bff9ca8e9e068660e86
Replacing Callee...
... 0xba7f588f9119e67968a7d6ab0110d79e1ecd3003e10cce3134018d42e966c8be
Callee: 0xd5c110c2f6566fd56749ec1a11328405f9935385
Saving successful migration to network...
... 0xc1526876e365189be1529a0943042e3a25e395a5cc19930ad907a694165104f1
Saving artifacts...
$ truffle console
truffle(development)> var caller = Caller.at('0xb8c5e079af71813acac73bff9ca8e9e068660e86');
undefined
truffle(development)> caller.someAction('0xd5c110c2f6566fd56749ec1a11328405f9935385');
{ [String: '250'] s: 1, e: 2, c: [ 250 ] }

How to call contract function from other contract in Ethereum

I have a deployed contract "greeter" in Ethereum
contract mortal {
address owner;
function mortal() { owner = msg.sender; }
function kill() { if (msg.sender == owner) selfdestruct(owner); }
}
contract greeter is mortal {
string greeting;
function greeter(string _greeting) public {
greeting = _greeting;
}
function greet() constant returns (string) {
return greeting;
}
}
And I want to create another contract, which will call function "kill" from my first contract. The main idea is that this 2 contracts is different. I publish contract A and then publish contract B, which calls A.
How I can do that?
Something like this, but from contract...
var contract = web3.eth.contract(contractAddress, ABI);
contract.call().kill();
Approximately like this, but there's a catch.
pragma solidity ^0.4.6;
contract Mortal {
address owner;
function Mortal() { owner = msg.sender; }
function kill() { if (msg.sender == owner) selfdestruct(owner); }
}
contract Greeter is Mortal {
string greeting;
function Greeter(string _greeting) public {
greeting = _greeting;
}
function greet() constant returns (string) {
return greeting;
}
}
contract Killer {
function destroyVictim(address victim) {
Greeter g = Greeter(victim);
g.kill();
}
}
That's a basic syntax. The ABI gets picked up by including Greeter in the source file along with Killer; that is, the compiler can "see it".
So far, so good.
The issue that arises is that Greeter is going to ignore the command, owing to if(msg.sender==owner). It won't be. It will be whatever address Killer got.
A possible solution to this before Greeter is deployed is to anticipate the need for a changeOwner() function, usually reserved for only the current owner.
Hope it helps.