其他案例
关于call的各种调用
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract MyToken {
uint256 public num;
string public message;
event Log(string message);
function foo(uint256 _num, string memory _message) external payable {
num = _num;
message = _message;
}
receive() external payable {
emit Log("receive was called");
}
fallback() external payable {
emit Log("fallback wass called");
}
}
interface IMyToken {
function foo(uint256 _num, string memory _message) external payable;
}
contract Call {
constructor() payable {}
bytes public data;
function callWithContract(
address payable target,
uint256 _fooNum,
string memory _fooMessage
) public {
MyToken(target).foo{value: 100}(_fooNum, _fooMessage);
}
function callWithParam(
MyToken target,
uint256 _fooNum,
string memory _fooMessage
) public {
target.foo{value: 100}(_fooNum, _fooMessage);
}
function callWithInterface(
address payable target,
uint256 _fooNum,
string memory _fooMessage
) public {
IMyToken(target).foo{value: 100}(_fooNum, _fooMessage);
}
function callWithSignature(
address target,
uint256 _fooNum,
string memory _fooMessage
) public {
(bool success, bytes memory _data) = target.call{value: 100}(
abi.encodeWithSignature("foo(uint256,string)", _fooNum, _fooMessage)
);
require(success, "call failed");
data = _data;
}
function callWithSelect(
address target,
uint256 _fooNum,
string memory _fooMessage
) public {
(bool success, bytes memory _data) = target.call{value: 100}(
abi.encodeWithSelector(
bytes4(keccak256(bytes("foo(uint256,string)"))),
_fooNum,
_fooMessage
)
);
require(success, "call failed");
data = _data;
}
function callWithCall(
address target,
uint256 _fooNum,
string memory _fooMessage
) public {
(bool success, bytes memory _data) = target.call{value: 100}(
abi.encodeCall(IMyToken(target).foo, (_fooNum, _fooMessage))
);
require(success, "call failed");
data = _data;
}
function noneCallWithData(address target, uint256 _none) public {
(bool success, bytes memory _data) = target.call{value: 100}(
abi.encodeWithSignature("none(uint256)", _none)
);
require(success, "none call failed");
data = _data;
}
function noneCallWithNone(address target) public {
(bool success, bytes memory _data) = target.call{value: 100}("");
require(success, "none call failed");
data = _data;
}
function callWitnSelfHsah(
address _target,
uint256 _value,
uint256 _num,
string memory _message
) public {
bytes memory data_tmp = abi.encodePacked(
bytes4(keccak256(bytes("foo(uint256,string)"))),
abi.encode(_num, _message)
);
(bool success, bytes memory _data) = _target.call{value: _value}(
data_tmp
);
require(success, "faile");
data = _data;
}
// 都是相等的
function test(uint256 x, string memory s)
public
pure
returns (
bytes memory,
bytes memory,
bytes memory,
bytes memory,
bytes memory,
bytes memory
)
{
bytes4 sel = bytes4(keccak256("foo(uint256,string)"));
bytes memory a = abi.encodeWithSelector(sel, x, s);
bytes memory b = bytes.concat(sel, abi.encode(x, s));
bytes memory c = abi.encodePacked(sel, abi.encode(x, s));
bytes memory d = abi.encodeWithSignature("foo(uint256,string)", x, s);
bytes memory e = abi.encodeCall(MyToken.foo, (x, s));
bytes memory f = abi.encodeCall(IMyToken.foo, (x, s));
return (a, b, c, d, e, f);
}
}
金库合约
核心原理
增加多少百分比的金额,就增发多少百分比的份额
推导过程
添加金额,获取股份
销毁股份,赎回金额
inflation Attack
核心:是solidity的向下取整机制,transfer转账,绕过了share记录 当计算得到小数时,由于solidity没有浮点数,所以会向下取整,整好把小数部分给省略了。0.xxx直接变为了0
function get(uint a, uint b) public pure returns(uint){
return a/b;
}
操作流程:攻击者在先存入小金额,获得一点shure,然后通过transfer的方式给合约转账巨额金额,使合约的B变大,因为上面我们知道s的计算是 aT的积除以B,所以,只要B足够大,就能使新计算的s是个小数,结果solidity向下取整为0,结果新用户就得不到s,而攻击者在取款的时候,a = sB/T,因为其他用户根本就没有获得过s所以,总的股份T=s,所以a = B,所以,金库合约的所以资金都属于攻击者了。
防范方法
1.通过独立的变量记录账户的余额
2.让合约第一个充值
完整代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "../common/IERC20.sol";
contract Vault {
IERC20 public immutable TOKEN;
uint256 public totalSupply;
mapping(address => uint) public balanceOf;
constructor(address _token){
TOKEN = IERC20(_token);
}
function _mint(address _to, uint _share) private{
totalSupply += _share;
balanceOf[_to] += _share;
}
function _burn(address _from, uint _share) private{
totalSupply -= _share;
balanceOf[_from] -= _share;
}
// s = at/b
function deposit(uint _amount) external {
require(_amount > 0, "Amount must be greater than 0");
uint share;
if (totalSupply == 0){
share = _amount;
}else{
share = _amount * totalSupply / TOKEN.balanceOf(address(this));
}
_mint(msg.sender, share);
require(TOKEN.transferFrom(msg.sender, address(this), _amount), "tansfer failed");
}
// a = sb/t
function withdraw(uint _share) external {
require(_share > 0, "Shares must be greater than 0");
uint amount = _share * TOKEN.balanceOf(address(this)) / totalSupply;
_burn(msg.sender, _share);
require(TOKEN.transfer(msg.sender, amount), "transfer failed");
}
}
恒和自动做市商
几个核心原则
兑换不改变流动性
添加、移除流动性不改变价格
流动性:代币对
添加流动性:添加的是一个代币对,是一对
价格:价格就是保证两个币的兑换比例一样
公式结论
兑换关系
提供多少个x,就能提取多少个y
添加流动性
提供 x,y 获取share
可以理解为:
移除流动性
可以理解为:
推导过程
添加流动性公式

移除流动性公式

完整代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract CSAMM{
IERC20 public immutable token0;
IERC20 public immutable token1;
uint public reserve0;
uint public reserve1;
uint public totalSupply;
mapping(address => uint) public balanceOf;
constructor(address _token0, address _token1){
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
function _mint(address _to, uint _amount) private {
balanceOf[_to] += _amount;
totalSupply += _amount;
}
function _burn(address _from, uint _amount) private {
balanceOf[_from] -= _amount;
totalSupply -= _amount;
}
function _update(uint _res0, uint _res1) private{
reserve0 = _res0;
reserve1 = _res1;
}
// dx = dy
function swap(address _tokenIn, uint256 _amountIn) external returns(uint256 amountOut){
require(_tokenIn == address(token0) || _tokenIn == address(token1), "invalid token");
require(_amountIn > 0, "amount in = 0");
bool isToken0 = _tokenIn == address(token0);
(IERC20 tokenIn, IERC20 tokenOut, uint256 resIn) = isToken0 ? (token0, token1, reserve0) : (token1, token0, reserve1);
require(tokenIn.transferFrom(msg.sender, address(this), _amountIn), "transfer failed");
// 不能直接相信_amountIn,有可能用户声称传入了100个,实际到了没有100个,还是用这种实际收到的计算比较靠谱
uint256 amountIn = tokenIn.balanceOf(address(this)) - resIn;
amountOut = (amountIn * 997) / 1000;
require(tokenOut.transfer(msg.sender, amountOut), "transfer failed");
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
// 公式里的xy就是reserve了,reserve表示转账之前的,通过balanceOf获得转账之后的
// shares = (dx + dy)/(x + y) * T
function addLiquidity(uint _amount0, uint _amount1) external returns(uint256 shares){
require(token0.transferFrom(msg.sender, address(this), _amount0), "transfer failed");
require(token1.transferFrom(msg.sender, address(this), _amount1), "transfer failed");
uint256 bal0 = token0.balanceOf(address(this));
uint256 bal1 = token1.balanceOf(address(this));
uint256 d0 = bal0 - reserve0; // 根据实际到账计算出来转入了多少币
uint256 d1 = bal1 - reserve1;
if (totalSupply > 0){
shares = (d0 + d1) * totalSupply / (reserve0 + reserve1);
}else{
shares = d0 + d1;
}
require(shares > 0, "shares = 0");
_mint(msg.sender, shares);
_update(bal0, bal1); // 重新更新当前两种币的余额
}
// d0 = s/T * B
function removeLiquidity(uint _shares) external returns(uint d0, uint d1){
d0 = (reserve0 * _shares) / totalSupply;
d1 = (reserve1 * _shares) / totalSupply;
_burn(msg.sender, _shares);
if (d0 > 0){
require(token0.transfer(msg.sender, d0), "transfer failed");
}
if (d1 >0){
require(token1.transfer(msg.sender, d1), "transfer failied");
}
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
// 手动更新reserve
function sync() external {
_update(token0.balanceOf(address(this)), token1.balanceOf(address(this)));
}
}
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount)
external
returns (bool);
function allowance(address owner, address spender)
external
view
returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount)
external
returns (bool);
}
恒积自动做市商
核心原则
依旧是这样
兑换不改变流动性
添加、移除流动性不改变价格
公式结论
兑换关系
可以理解为:
添加流动性
提供 x,y 获取share
用y来表示为:
移除流动性
可以理解为:
推导过程
兑换关系

添加流动性

移除流动性

金库,恒和,恒积对比
| 兑换关系 | 添加流动性 | 移除流动性 | |
|---|---|---|---|
| 金库合约 | 无 | ||
| 恒和做市商 | |||
| 恒积做市商 |
总结
恒和、恒积的兑换关系,遵循自己的恒和恒积。
添加流动性,移除流动性,跟别根据自己的流动性表达式,添加流动性的表达式,再结合价格不变原理,带入到金库合约的公式中,即可计算出结果。
兑换关系
恒和:
恒积:
流动性:
恒和:
恒积:
添加流动性
恒和:
恒积:
价格不变原理
不论是恒和,恒积,始终是这个: