095 MoreAdvancedSolidity 2
095 MoreAdvancedSolidity 2
1
On
2
Advanced?
3
What was Advanced Solidity back in 2018?
https://www.youtube.com/watch?v=VhzafmGGmzo&t=10s
4
2018 Advanced Solidity and Design Patterns
5
Advanced?
6
Agenda
● Cross-contract calls.
● Upgrade pattern using Open Zeppelin’s upgradable contract.
● Hidden parameters.
7
Cross-Contract Calls
8
Cross-Contract Calls
Contract A Contract B
Externally
Owned
Account
(EOA)
Contract A Contract B
Externally
Owned
Account
(EOA)
msg.sender == EOA msg.sender == Contract A
Context: Contract A Context: Contract B
Contract A Contract B
Externally
Owned
Account
(EOA)
msg.sender == EOA msg.sender == Contract A
Context: Contract A Context: Contract A
Contract A Contract B
Externally
Owned
Account
(EOA)
msg.sender == EOA msg.sender == EOA
Context: Contract A Context: Contract A
Contract A Contract B
Externally
Owned
Account
(EOA)
msg.sender == EOA msg.sender == Contract A
Context: Contract A Context: Contract B
contract ContractA {
uint256 public val1;
uint256 public val2;
}
contract ContractB {
uint256 public bVal;
address public addr;
mapping(address => uint256) public balances;
}
14
Memory Layout
0 val1 bVal
1 val2 addr
2 balances
15
Memory Layout: Delegate Call
0 val1 bVal
1 val2 addr
2 balances
16
Call Types
17
Cross-Contract Calls
More Detail
18
Cross-Contract Calls
contract ContractA {
ContractB immutable public conB;
uint256 public val;
constructor (address _conB) {
conB = ContractB(_conB);
}
function callStuff1(bool _fail) external {
val = conB.stuff1(_fail);
}}
contract ContractB {
uint256 public bVal;
contract ContractA {
ContractB immutable public conB;
uint256 public val;
constructor (address _conB) {
conB = ContractB(_conB);
}
function callStuff1(bool _fail) external {
val = conB.stuff1(_fail);
}}
contract ContractB {
uint256 public bVal;
val = conB.stuff1(_fail);
PC: 0x117, opcode: JUMPDEST
PC: 0x118, opcode: POP otherwise forward the revert message.
PC: 0x119, opcode: GAS
PC: 0x11a, opcode: CALL The revert message from ContractB is in
PC: 0x11b, opcode: ISZERO the return data.
PC: 0x11c, opcode: DUP1
PC: 0x11d, opcode: ISZERO Copy the data returned from Contract B to
PC: 0x11e, opcode: PUSH2 0x012b memory. Push onto the stack the return
PC: 0x121, opcode: JUMPI data size and location of the data.
PC: 0x122, opcode: RETURNDATASIZE
PC: 0x123, opcode: PUSH1 0x00
PC: 0x125, opcode: DUP1
PC: 0x126, opcode: RETURNDATACOPY
PC: 0x127, opcode: RETURNDATASIZE
PC: 0x128, opcode: PUSH1 0x0
PC: 0x12a, opcode: REVERT
....
24
Coding that into Solidity
33
Reverts and Panics
34
Panics
... } block.
● 0x12; If you divide or modulo by zero (e.g. 5 / 0 or 23 % 0 ).
● 0x21: If you convert a value that is too big or negative into an enum type.
● 0x22: If you access a storage byte array that is incorrectly encoded.
● 0x31: If you call .pop() on an empty array.
● 0x32: If you access an array, bytesN or an array slice at an out-of-bounds or negative index
(i.e. x[i] where i >= x.length or i < 0 ).
● 0x41: If you allocate too much memory or create an array that is too large.
● 0x51: If you call a zero-initialized variable of internal function type.
https://docs.soliditylang.org/en/v0.8.11/control-structures.html#panic-via-assert-and-error-via-require 35
Custom Errors
36
Try / Catch
37
Try / Catch with Custom Error
38
OpenZeppelin Upgrade
Proxy
39
Proxy
40
Delegate Call
Contract A Contract B
Externally
Owned
Account
(EOA)
msg.sender == EOA msg.sender == EOA
Context: Contract A Context: Contract A
Proxy Contract B
Externally
Owned
Account
(EOA)
msg.sender == EOA msg.sender == EOA
Context: Proxy Context: Proxy
○ function implementation()
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/transparent/TransparentUpgradeableProxy.sol 43
Delegate Call
Proxy
function admin()
…
Contract B
fallback()
Externally
Owned
Account
(EOA)
msg.sender == EOA msg.sender == EOA
Context: Proxy Context: Proxy
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
} 45
See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol
Proxy
Copy call data to memory, over-writing
whatever is at memory location 0x00.
fallback() external payable virtual {
address impl = getImplAddr();
assembly {
calldatacopy(0, 0, calldatasize())
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
} 46
See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol
Proxy
Call the implementation, specifying the
memory location and the size of the call
data.
fallback() external payable virtual {
address impl = getImplAddr();
assembly {
calldatacopy(0, 0, calldatasize())
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
} 47
See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol
Proxy
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
} 48
See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol
Proxy
Return the information at memory location
0, the length of the return data, in a revert
of return, depending on whether
fallback() external payable virtual { delegatecall reverted or not.
address impl = getImplAddr();
assembly {
calldatacopy(0, 0, calldatasize())
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
} 49
See: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol
Memory Layout
0 bVal
1 addr
2 balances
0x360894a13ba1a32106 Implementation
67c828492db98dca3e20
76cc3735a920a3ca505d
382bbc
50
Memory Layout
0 bVal bVal
1 addr addr
2 balances balances
3 pause
0x360894a13ba1a Implementation
3210667c828492d
b98dca3e2076cc3
735a920a3ca505d
382bbc
51
What about more complex code? Version 1
52
Memory Layout Version 1
0 val1
1 val2
2 val4
3 val5
4 val7
5 val8
0x360894a13b… Implementation
53
What about more complex code? Version 1
2
54
Memory Layout Version 2
0 val1 val1
1 val2 val2
2 val4 val3
3 val5 val4
4 val7 val5
5 val8 val7
6 val8
0x360894a13b… Implementation
55
What about more complex code? Revised Version 1
0 val1
1 val2
2 dummy0
3 val4
4 val5
5 dummy1
6 val7
7 val8
0x360894a13b… Implementation
57
What about more complex code? Revised Version 2
0 val1 val1
1 val2 val2
2 dummy0 val3
3 val4 val4
4 val5 val5
5 dummy1 dummy1
6 val7 val7
7 val8 val8
0x360894a13b… Implementation
59
Upgradable Contracts
Good idea?
Bad idea?
60
Upgradable Contracts
● You have to trust the “owner” of an upgradable contract to not “bait and switch” contract logic.
● Note that “owner” could be a multi-sig wallet, or could be a single EOA.
61
Hidden Parameters / Hidden
Context
62
Hidden Parameters
● Function signature:
function transfer(address recipient, uint256 amount) external returns (bool);
63
Hidden Params
contract Source {
function callFunc(uint256 _val) external {
dest.func(11);
}
}
contract Dest {
function func(uint256 _val) external {
...
}
}
64
Hidden Params
Call data:
bytes4 bytes
Function
ABI encoded parameters
Selector
Function
ABI encoded parameters ABI encoded hidden parameters
Selector
65
Hidden Params Create the function selector and
parameters.
contract Source {
function callFunc(uint256 _val) external {
bytes memory funcCall = abi.encodeWithSelector(dest.funcOneParam.selector, 11)
bytes memory authVals = abi.encodePacked(v1, v2);
bytes memory functionCallWithAuth = bytes.concat(funcCall, authVals);
bool isSuccess;
bytes memory returnValueEncoded;
(isSuccess, returnValueEncoded) = address(dest).call(functionCallWithAuth);
if (!isSuccess) {
revert(getRevertMsg(returnValueEncoded));
}
}
}
contract Dest {
function func(uint256 _val) external {
...
} 66
Hidden Params
Encode hidden parameters.
contract Source {
function callFunc(uint256 _val) external {
bytes memory funcCall = abi.encodeWithSelector(dest.funcOneParam.selector, 11)
bytes memory authVals = abi.encodePacked(v1, v2);
bytes memory functionCallWithAuth = bytes.concat(functCall, authVals);
bool isSuccess;
bytes memory returnValueEncoded;
(isSuccess, returnValueEncoded) = address(dest).call(functionCallWithAuth);
if (!isSuccess) {
revert(getRevertMsg(returnValueEncoded));
}
}
}
contract Dest {
function func(uint256 _val) external {
...
} 67
Hidden Params Combine the function call and hidden
parameters
contract Source {
function callFunc(uint256 _val) external {
bytes memory funcCall = abi.encodeWithSelector(dest.funcOneParam.selector, 11)
bytes memory authVals = abi.encodePacked(v1, v2);
bytes memory functionCallWithAuth = bytes.concat(funcCall, authVals);
bool isSuccess;
bytes memory returnValueEncoded;
(isSuccess, returnValueEncoded) = address(dest).call(functionCallWithAuth);
if (!isSuccess) {
revert(getRevertMsg(returnValueEncoded));
}
}
}
contract Dest {
function func(uint256 _val) external {
...
} 68
Hidden Params
Do the function call and process reverts.
contract Source {
function callFunc(uint256 _val) external {
bytes memory funcCall = abi.encodeWithSelector(dest.funcOneParam.selector, 11)
bytes memory authVals = abi.encodePacked(v1, v2);
bytes memory functionCallWithAuth = bytes.concat(funcCall, authVals);
bool isSuccess;
bytes memory returnValueEncoded;
(isSuccess, returnValueEncoded) = address(dest).call(functionCallWithAuth);
if (!isSuccess) {
revert(getRevertMsg(returnValueEncoded));
}
}
}
contract Dest {
function func(uint256 _val) external {
...
} 69
Hidden Params Copy the variables from the end of the call
data.
contract Dest {
function func(uint256 _val) external {
uint256 v1;
uint256 v2;
bytes calldata allParams = msg.data;
uint256 len = allParams.length;
assembly {
calldatacopy(0x0, sub(len, 64), 32)
v1 := mload(0)
calldatacopy(0x0, sub(len, 32), 32)
v2 := mload(0)
}
...
Note that you could check that the length
}
of call data is long enough to contain the
}
hidden parameters prior to executing this
code.
70
Hidden Params
My use-case:
● In GPACT crosschain system, hidden parameters are used to add crosschain authentication
parameters.
● Users program against a contract’s API.
● Crosschain control contract injects the authentication parameters prior to calling the contract.
It is much cheaper to store information in call data than to temporarily store information in storage.
71
Questions
?
72
You Tube, Slack, Meet-up, Example Code
YouTube: https://www.youtube.com/c/ethereumengineeringgroup
Meet-up: https://www.meetup.com/ethereum-engineering/
73
Future Talks & Events
74