In this post, we’re going to see how we can call another contract function. And we talk about more deeply about delegatecall
When writing Ethereum Smart Contract code, there are some cases when we need to interact with other contract. In Solidity, for this purpose, there’s several ways to achieve this goal.
If we know target contract ABI, we can directly use function signature
Let’s say we have deployed simple contract called “Storage” that allows user to save a value.
pragma solidity ^0.5.8;
contract Storage {
uint public val;
constructor(uint v) public {
val = v;
}
function setValue(uint v) public {
val = v;
}
}
And we want to deploy another contract called “Machine” which is caller of “Storage” contract. “Machine” reference “Storage” contract and change its value.
pragma solidity ^0.5.8;
import "./Storage.sol";
contract Machine {
Storage public s;
constructor(Storage addr) public {
s = addr;
calculateResult = 0;
}
function saveValue(uint x) public returns (bool) {
s.setValue(x);
return true;
}
function getValue() public view returns (uint) {
return s.val();
}
}
In this case, we know the ABI of “Storage” and its address, so that we can initialise existing “Storage” contract with the address and ABI tells us how we can call “Storage” contract’s function. We can see “Machine” contract call “Storage” setValue()
function.
And write a test code to check whether “Machine” saveValue()
function actually calls “Storage” setValue()
function and change its state.
const StorageFactory = artifacts.require('Storage');
const MachineFactory = artifacts.require('Machine');
contract('Machine', accounts => {
const [owner, ...others] = accounts;
beforeEach(async () => {
Storage = await StorageFactory.new(new BN('0'));
Machine = await MachineFactory.new(Storage.address);
});
describe('#saveValue()', () => {
it('should successfully save value', async () => {
await Machine.saveValue(new BN('54'));
(await Storage.val()).should.be.bignumber.equal(new BN('54'));
});
});
});
And test passed!
Contract: Machine
After initalize
#saveValue()
✓ should successfully save value (56ms)
1 passing (56ms)
If we don’t know target contract ABI, use call or delegatecall
But what if caller (in this case “Machine” contract) doesn’t know the ABI of target contract?
Still we can call target contract’s function with call()
and delegatecall()
.
Before explain about Ethereum Solidity call()
and delegatecall()
, it would be helpful to see how EVM saves contract’s variables to understand call()
and delegatecall()
.
How EVM saves field variables to Storage
In Ethereum, there’s two kinds of space for saving contract’s field variables. One is “memory” and the other is “storage”. And what ‘foo is saved to storage’ means that the value of ‘foo’ is permanently recorded to states.
Then how could so many variables in single contract not overlap each other’s address space? EVM assign slot number to the field variables.
contract Sample1 {
uint256 first; // slot 0
uint256 second; // slot 1
}

Because first
is declared first in “Sample1” it is assigned 0 slot. Each different variables distinguished by its slot number.
In EVM it has 2256 slot in Smart Contract storage and each slot can save 32byte size data.
How Smart Contract Function is Called
Like general programming code such as Java, Python, Solidity function can be seen as group of commands. When we say ‘function is called’, it means that we inject specific context (like arguments) to that group of commands (function) and commands are executed one by one with this context.
And function, group of commands, address space can be found by its name.
In Ethereum function call can be expressed by bytecode as long as 4 + 32 * N bytes. And this bytecode consist of two parts.
- Function Selector: This is first 4 bytes of function call’s bytecode. This is generated by hashing target function’s name plus with its arguments type excluding empty space. For example
savaValue(uint)
. Currently Ethereum use keccak-256 hashing function to create function selector. Based on this function selector, EVM can decide which function should be called in the contract. - Function Argument: Convert each value of arguments into hex string with the fixed length of 32bytes. If there is more than one argument, concatenate
If user pass this 4 + 32 * N bytes bytecode to the data field of transaction. EVM can find which function should be executed then inject arguments to that function.
Explain DelegateCall with test case
Context
There’s a word “context” when we talked about how smart contract function is called. Actually the word “context” is much general concept in software and the meaning is changed little bit depending on the context.
When we talked about execution of program, we can say “context” as all the environment like variable or states at the point of execution. For example, on the point of exeuction of program ‘A’, the username who execute that program is ‘zeroFruit’, then username ‘zeroFruit’ can be context of program ‘A’.
In the Ethereum Smart Contract, there is lots of context and one representative thing is ‘who execute this contract’. You may be seen msg.sender
a lot in Solidity code and the value of msg.sender
address vary depending on who execute this contract function.
DelegateCall
DelegateCall, as name implies, is calling mechanism of how caller contract calls target contract function but when target contract exeucted its logic, the context is not on the user who execute caller contract but on caller contract.








Then when contract delegatecall to target, how state of storage would be changed?
Because when delegatecall to target, the context is on Caller contract, all state change logics reflect on Caller’s storage.
For example, let’s there’s Proxy contract and Business contract. Proxy contract delegatecall to Business contract function. If user call Proxy contract, Proxy contract will delegatecall to Business contract and function would be executed. But all state changes will be reflect to Proxy contract storage, not Business contract.
Test case
This is extension version of contract explained before. It still has “Storage” as field and addValuesWithDelegateCall
, addValuesWithCall
in addition to test how storage would be changed. And “Machine” has calculateResult
, user
for saving add result and who called this function each.
pragma solidity ^0.5.8;
import "./Storage.sol";
contract Machine {
Storage public s;
uint256 public calculateResult;
address public user;
event AddedValuesByDelegateCall(uint256 a, uint256 b, bool success);
event AddedValuesByCall(uint256 a, uint256 b, bool success);
constructor(Storage addr) public {
...
calculateResult = 0;
}
...
function addValuesWithDelegateCall(address calculator, uint256 a, uint256 b) public returns (uint256) {
(bool success, bytes memory result) = calculator.delegatecall(abi.encodeWithSignature("add(uint256,uint256)", a, b));
emit AddedValuesByDelegateCall(a, b, success);
return abi.decode(result, (uint256));
}
function addValuesWithCall(address calculator, uint256 a, uint256 b) public returns (uint256) {
(bool success, bytes memory result) = calculator.call(abi.encodeWithSignature("add(uint256,uint256)", a, b));
emit AddedValuesByCall(a, b, success);
return abi.decode(result, (uint256));
}
}
And this is our target contract “Calculator”. It also has calculateResult
and user
.
pragma solidity ^0.5.8;
contract Calculator {
uint256 public calculateResult;
address public user;
event Add(uint256 a, uint256 b);
function add(uint256 a, uint256 b) public returns (uint256) {
calculateResult = a + b;
assert(calculateResult >= a);
emit Add(a, b);
user = msg.sender;
return calculateResult;
}
}
addValuesWithCall
And this is our addValuesWithCall
test code. What we need to test is
- Because context is on “Calculator” not “Machine”, add result should be save into “Calculator” storage
- So “Calculator”
calculateResult
should be 3, anduser
address should set to “Machine” address. - And “Machine”
calculateResult
should be 0, anduser
to ZERO address.
- So “Calculator”
describe('#addValuesWithCall()', () => {
let Calculator;
beforeEach(async () => {
Calculator = await CalculatorFactory.new();
});
it('should successfully add values with call', async () => {
const result = await Machine.addValuesWithCall(Calculator.address, new BN('1'), new BN('2'));
expectEvent.inLogs(result.logs, 'AddedValuesByCall', {
a: new BN('1'),
b: new BN('2'),
success: true,
});
(result.receipt.from).should.be.equal(owner.toString().toLowerCase());
(result.receipt.to).should.be.equal(Machine.address.toString().toLowerCase());
(await Calculator.calculateResult()).should.be.bignumber.equal(new BN('3'));
(await Machine.calculateResult()).should.be.bignumber.equal(new BN('0'));
(await Machine.user()).should.be.equal(constants.ZERO_ADDRESS);
(await Calculator.user()).should.be.equal(Machine.address);
});
});
And test pass as expected!
Contract: Machine
After initalize
#addValuesWithCall()
✓ should successfully add values with call (116ms)
1 passing (116ms)
addValuesWithDelegateCall
And this is our addValuesWithCall
test code. What we need to test is
- Because context is on “Machine” not “Calculator”, add result should be save into “Calculator” storage
- So “Calculator”
calculateResult
should be 0, anduser
address should set to ZERO address. - And “Machine”
calculateResult
should be 3, anduser
to EOA.
- So “Calculator”
describe('#addValuesWithDelegateCall()', () => {
let Calculator;
beforeEach(async () => {
Calculator = await CalculatorFactory.new();
});
it('should successfully add values with delegate call', async () => {
const result = await Machine.addValuesWithDelegateCall(Calculator.address, new BN('1'), new BN('2'));
expectEvent.inLogs(result.logs, 'AddedValuesByDelegateCall', {
a: new BN('1'),
b: new BN('2'),
success: true,
});
(result.receipt.from).should.be.equal(owner.toString().toLowerCase());
(result.receipt.to).should.be.equal(Machine.address.toString().toLowerCase());
// Calculator storage DOES NOT CHANGE!
(await Calculator.calculateResult()).should.be.bignumber.equal(new BN('0'));
// Only calculateResult in Machine contract should be changed
(await Machine.calculateResult()).should.be.bignumber.equal(new BN('3'));
(await Machine.user()).should.be.equal(owner);
(await Calculator.user()).should.be.equal(constants.ZERO_ADDRESS);
});
});
But FAILED! What??? Where ‘562046206989085878832492993516240920558397288279’ come from?
0 passing (236ms)
1 failing
1) Contract: Machine
After initalize
#addValuesWithDelegateCall()
should successfully add values with delegate call:
AssertionError: expected '562046206989085878832492993516240920558397288279' to equal '3'
+ expected - actual
-562046206989085878832492993516240920558397288279
+3
As we mentioned before each field variable has its own slot. And when we delegatecall “Calculator”, the context is on “Machine”, but the slot number is based on “Calculator”. So because “Calculator” logic override Storage
address with calculateResult
, so as calculateResult
to user
, test failed.
Based on this knowledge, we can find where ‘562046206989085878832492993516240920558397288279’ come from. It is decimal version of EOA.




So to fix this problem, we need to change the order of “Machine” field variable.
contract Machine {
uint256 public calculateResult;
address public user;
Storage public s;
...
}
And finally test passed!
Contract: Machine
After initalize
#addValuesWithDelegateCall()
✓ should successfully add values with delegate call (106ms)
1 passing (247ms)
Wrap up
In this post, we’ve seen how we can call another contract’s function from contract.
- If we know the ABI of target function, we can directly use target function signature
- If we don’t know the ABI of target function, we can use
call()
, ordelegatecall()
. But in the case ofdelegatecall()
, we need to care about the order of field variable.
Source code
If you want to test on your own, your can find the code on this repository.
https://github.com/zeroFruit/upgradable-contract/tree/feat/delegatecall