Skip to content

Simplified donation attack prevention #2453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 82 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
2839dc1
intermittent work committed
sparrowDom Jun 21, 2024
2e6be62
add draft solution for preparing WOETH for the landing markets
sparrowDom Jun 21, 2024
5495d4b
add tests that confirm lending market protection works as exepcted
sparrowDom Jun 24, 2024
d0eb16f
add test to protect agains calling initialize twice
sparrowDom Jun 24, 2024
fc5d874
add comments
sparrowDom Jun 26, 2024
cfec4e5
remove unneeded files
sparrowDom Jun 26, 2024
8405bfc
minor bug fix
sparrowDom Jun 26, 2024
e03223c
add fork script tests and fix bug
sparrowDom Jun 26, 2024
319467f
add comments
sparrowDom Jun 26, 2024
1142a74
add another test
sparrowDom Jun 26, 2024
f911ba6
divide after multiply
sparrowDom Jun 26, 2024
3dc8523
change credits per token to highres
sparrowDom Jun 26, 2024
bad282b
add comment
sparrowDom Jun 26, 2024
07b51cf
comments
sparrowDom Jun 26, 2024
c0333fb
lint
sparrowDom Jun 26, 2024
2c965c3
minor changes
sparrowDom Jun 26, 2024
df57ef7
Merge remote-tracking branch 'origin/master' into sparrowDom/woeth_ha…
sparrowDom Jun 26, 2024
13fd4da
correct fixture
sparrowDom Jun 26, 2024
7923ddb
make code slighlty better regarding re-entry
sparrowDom Jun 26, 2024
c69583f
prettier
sparrowDom Jun 26, 2024
6822937
add explicit visibility
sparrowDom Jun 27, 2024
ac855d9
minor test enhancement
sparrowDom Jun 27, 2024
1c9fc64
better function name
sparrowDom Jul 1, 2024
56d0b7d
remove comment
sparrowDom Jul 1, 2024
057469c
simplify
sparrowDom Dec 17, 2024
1a6a16f
simplify code
sparrowDom Dec 17, 2024
2223600
simplify code
sparrowDom Dec 17, 2024
b34d811
Merge remote-tracking branch 'origin/master' into sparrowDom/woeth_ha…
sparrowDom Dec 17, 2024
39e8d4b
refactor
sparrowDom Dec 17, 2024
3615ef7
add redeem all test
sparrowDom Dec 17, 2024
466f957
prettier
sparrowDom Dec 17, 2024
52eb244
Add wOETH donation fork tests (#2122)
shahthepro Dec 17, 2024
f0580bc
rename deploy script
sparrowDom Dec 17, 2024
0a988d5
add another test and simplify constructor
sparrowDom Dec 17, 2024
e2eb8cf
fix: specify override for name and symbol.
clement-ux Feb 18, 2025
c6ded55
fix: import IERC20Metadata.
clement-ux Feb 18, 2025
be6d473
add gap
sparrowDom Jan 6, 2025
c38a3c6
update reference to code
sparrowDom Feb 24, 2025
66269c8
fix compile errors
sparrowDom Feb 24, 2025
5a7192d
move initialize2() into primary initializer
sparrowDom Feb 24, 2025
5ff5c5d
remove virtual where not needed
sparrowDom Feb 24, 2025
a05eaa5
Merge remote-tracking branch 'origin/master' into sparrowDom/woeth_ha…
sparrowDom Feb 24, 2025
3ef2219
pick a more appropriate method for fetching highres CPT
sparrowDom Feb 24, 2025
637cd3c
round in the favour of the protocol
sparrowDom Feb 24, 2025
390e4a2
Revert "round in the favour of the protocol"
sparrowDom Feb 24, 2025
aad7b57
rounding error fixes
sparrowDom Feb 25, 2025
c005824
Option 1 for WOETH rounding error (#2419)
sparrowDom Feb 26, 2025
36c2ce3
prettier
sparrowDom Feb 26, 2025
6791cf1
add require 0 checks
sparrowDom Feb 26, 2025
abd7fbe
allow 0 value mints and redeems
sparrowDom Feb 26, 2025
79af39f
Merge remote-tracking branch 'origin/master' into sparrowDom/woeth_ha…
sparrowDom Feb 28, 2025
503d22b
to establish exchange rate for WOETH trust OETH completely
sparrowDom Mar 19, 2025
7d15686
remove unneeded overrides
sparrowDom Mar 19, 2025
11fa68c
add comment and prettier
sparrowDom Mar 19, 2025
f7b4953
fix a denomination bug
sparrowDom Mar 19, 2025
1b2a44b
add fork test for the expected rate increase
sparrowDom Mar 19, 2025
8635ee9
add comment
sparrowDom Mar 19, 2025
1297d24
add tests for WOETH upgrade not changing the existing WOETH balances
sparrowDom Mar 19, 2025
418eae5
denominate all rates to 1e27 for better precision
sparrowDom Mar 19, 2025
3cd9322
undo the change
sparrowDom Mar 19, 2025
c93e12e
add comment
sparrowDom Mar 20, 2025
80bea89
further simplify the WOETH rate calculation code
sparrowDom Mar 21, 2025
6fc7f8e
add comments
sparrowDom Mar 24, 2025
f2b2a59
prettier
sparrowDom Mar 24, 2025
8b237c0
update test to confirm that redemption amounts of WOETH before and af…
sparrowDom Mar 24, 2025
ebd7d3e
Fix bug with rounding direction on deposits
DanielVF Apr 9, 2025
0eb41aa
nit: unify comment
sparrowDom Apr 10, 2025
1765e26
sligth code simplification
sparrowDom Apr 10, 2025
14cc62b
fix typo
sparrowDom Apr 10, 2025
c97a327
remove unused imports
sparrowDom Apr 10, 2025
ca1a998
remove todo
sparrowDom Apr 10, 2025
3c25cee
switch to BUSL
sparrowDom Apr 10, 2025
599c415
improve comment
sparrowDom Apr 10, 2025
2f3f003
no need to call governable anymore
sparrowDom Apr 10, 2025
65f8eb0
fix comment
sparrowDom Apr 10, 2025
191f522
make WOSonic extend WOETH. Add deploy file and basic fork test
sparrowDom Apr 10, 2025
a2a69c9
use the more appropriate natspec compliant inheritdoc
sparrowDom Apr 10, 2025
3d2d0fd
more appropriate naming
sparrowDom Apr 11, 2025
bdc0209
add aditiona test
sparrowDom Apr 11, 2025
19854fa
Merge remote-tracking branch 'origin/master' into sparrowDom/woeth_do…
sparrowDom Apr 14, 2025
6504385
make WOUSD inherit from WOETH
sparrowDom Apr 14, 2025
87f0828
add deploy and fork test file for WOUSD
sparrowDom Apr 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
662 changes: 662 additions & 0 deletions brownie/abi/ERC4626.json

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions brownie/scripts/woeth_manipulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from world import *

def expect_approximate(woeth_holder, expected_balance):
balance = woeth.balanceOf(woeth_holder)
diff = abs(expected_balance - balance)
if (diff != 0):
raise Exception("Unexpected balance for account: %s".format(woeth_holder))

def confirm_balances_after_upgrade():
expect_approximate("0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb", 1013453939109688661944)
expect_approximate("0xC460B0b6c9b578A4Cb93F99A691e16dB96Ee5833", 575896531839923556165)
expect_approximate("0xdca0a2341ed5438e06b9982243808a76b9add6d0", 319671606657733042618)
expect_approximate("0x8a9d46d28003673cd4fe7a56ecfcfa2be6372e64", 182355401624955452064)
expect_approximate("0xf65ecb5610000100befba41b9f9cf5ca32838078", 97352556026536192865)
expect_approximate("0x0a26e7ab5c554232314a8d459eff0ede72333f08", 91628532171545105831)


def manipulate_price():
OETH_WHALE="0xa4C637e0F704745D182e4D38cAb7E7485321d059"
whl = {'from': OETH_WHALE }

woeth.convertToAssets(1e18) / 1e18
oeth.transfer(woeth.address, 10_000 * 1e18, whl)
woeth.convertToAssets(1e18) / 1e18

oeth.approve(woeth.address, 1e50, whl)
woeth.deposit(5_000 * 1e18, OETH_WHALE, whl)
1 change: 1 addition & 0 deletions brownie/world.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
weth = load_contract('ERC20', WETH)
ousd = load_contract('ousd', OUSD)
oeth = load_contract('ousd', OETH)
woeth = load_contract('erc4626', WOETH)
usdt = load_contract('usdt', USDT)
usdc = load_contract('usdc', USDC)
dai = load_contract('dai', DAI)
Expand Down
6 changes: 1 addition & 5 deletions contracts/contracts/mocks/MockLimitedWrappedOusd.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import { WrappedOusd } from "../token/WrappedOusd.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockLimitedWrappedOusd is WrappedOusd {
constructor(
ERC20 underlying_,
string memory name_,
string memory symbol_
) WrappedOusd(underlying_, name_, symbol_) {}
constructor(ERC20 underlying_) WrappedOusd(underlying_) {}

function maxDeposit(address)
public
Expand Down
86 changes: 78 additions & 8 deletions contracts/contracts/token/WOETH.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { ERC4626 } from "../../lib/openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
Expand All @@ -12,24 +12,63 @@ import { Initializable } from "../utils/Initializable.sol";
import { OETH } from "./OETH.sol";

/**
* @title OETH Token Contract
* @title Wrapped OETH Token Contract
* @author Origin Protocol Inc
*
* @dev An important capability of this contract is that it isn't susceptible to changes of the
* exchange rate of WOETH/OETH if/when someone sends the underlying asset (OETH) to the contract.
* If OETH weren't rebasing this could be achieved by solely tracking the ERC20 transfers of the OETH
* token on mint, deposit, redeem, withdraw. The issue is that OETH is rebasing and OETH balances
* will change when the token rebases.
* For that reason the contract logic checks the actual underlying OETH token balance only once
* (either on a fresh contract creation or upgrade) and considering the WOETH supply and
* rebasingCreditsPerToken calculates the _adjuster. Once the adjuster is calculated any donations
* to the contract are ignored. The totalSupply (instead of querying OETH balance) works off of
* adjuster the current WOETH supply and rebasingCreditsPerToken. This makes WOETH value accrual
* completely follow OETH's value accrual.
* WOETH is safe to use in lending markets as the VualtCore's _rebase contains safeguards preventing
* any sudden large rebases.
*/

contract WOETH is ERC4626, Governable, Initializable {
using SafeERC20 for IERC20;
/* This is a 1e27 adjustment constant that expresses the difference in exchange rate between
* OETH's rebase since inception (expressed with rebasingCreditsPerToken) and WOETH to OETH
* conversion.
*
* If WOETH and OETH are deployed at the same time, the value of adjuster is a neutral 1e27
*/
uint256 public adjuster;
uint256[49] private __gap;

constructor(
ERC20 underlying_,
string memory name_,
string memory symbol_
) ERC20(name_, symbol_) ERC4626(underlying_) Governable() {}
// no need to set ERC20 name and symbol since they are overridden in WOETH & WOETHBase
constructor(ERC20 underlying_) ERC20("", "") ERC4626(underlying_) {}

/**
* @notice Enable OETH rebasing for this contract
*/
function initialize() external onlyGovernor initializer {
OETH(address(asset())).rebaseOptIn();

initialize2();
}

/**
* @notice secondary initializer that newly deployed contracts will execute as part
* of primary initialize function and the existing contracts will have it called
* as a governance operation.
*/
function initialize2() public onlyGovernor {
require(adjuster == 0, "Initialize2 already called");

if (totalSupply() == 0) {
adjuster = 1e27;
} else {
adjuster =
(rebasingCreditsPerTokenHighres() *
ERC20(asset()).balanceOf(address(this))) /
totalSupply();
}
}

function name()
Expand Down Expand Up @@ -62,7 +101,38 @@ contract WOETH is ERC4626, Governable, Initializable {
external
onlyGovernor
{
require(asset_ != address(asset()), "Cannot collect OETH");
require(asset_ != address(asset()), "Cannot collect core asset");
IERC20(asset_).safeTransfer(governor(), amount_);
}

/// @inheritdoc ERC4626
function convertToShares(uint256 assets)
public
view
virtual
override
returns (uint256 shares)
{
return (assets * rebasingCreditsPerTokenHighres()) / adjuster;
}

/// @inheritdoc ERC4626
function convertToAssets(uint256 shares)
public
view
virtual
override
returns (uint256 assets)
{
return (shares * adjuster) / rebasingCreditsPerTokenHighres();
}

/// @inheritdoc ERC4626
function totalAssets() public view override returns (uint256) {
return (totalSupply() * adjuster) / rebasingCreditsPerTokenHighres();
}

function rebasingCreditsPerTokenHighres() internal view returns (uint256) {
return OETH(asset()).rebasingCreditsPerTokenHighres();
}
}
6 changes: 2 additions & 4 deletions contracts/contracts/token/WOETHBase.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { WOETH } from "./WOETH.sol";
Expand All @@ -10,9 +10,7 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
*/

contract WOETHBase is WOETH {
constructor(ERC20 underlying_)
WOETH(underlying_, "Wrapped Super OETH", "wsuperOETHb")
{}
constructor(ERC20 underlying_) WOETH(underlying_) {}

function name() public view virtual override returns (string memory) {
return "Wrapped Super OETH";
Expand Down
44 changes: 6 additions & 38 deletions contracts/contracts/token/WOSonic.sol
Original file line number Diff line number Diff line change
@@ -1,41 +1,23 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { ERC4626 } from "../../lib/openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { Governable } from "../governance/Governable.sol";
import { Initializable } from "../utils/Initializable.sol";
import { OSonic } from "./OSonic.sol";
import { WOETH } from "./WOETH.sol";

/**
* @title Wrapped Origin Sonic (wOS) token on Sonic
* @author Origin Protocol Inc
*/
contract WOSonic is ERC4626, Governable, Initializable {
using SafeERC20 for IERC20;

constructor(
ERC20 underlying_,
string memory name_,
string memory symbol_
) ERC20(name_, symbol_) ERC4626(underlying_) Governable() {}

/**
* @notice Enable Origin Sonic rebasing for this contract
*/
function initialize() external onlyGovernor initializer {
OSonic(address(asset())).rebaseOptIn();
}
contract WOSonic is WOETH {
constructor(ERC20 underlying_) WOETH(underlying_) {}

function name()
public
view
virtual
override(ERC20, IERC20Metadata)
override(WOETH)
returns (string memory)
{
return "Wrapped OS";
Expand All @@ -45,23 +27,9 @@ contract WOSonic is ERC4626, Governable, Initializable {
public
view
virtual
override(ERC20, IERC20Metadata)
override(WOETH)
returns (string memory)
{
return "wOS";
}

/**
* @notice Transfer token to governor. Intended for recovering tokens stuck in
* contract, i.e. mistaken sends. Cannot transfer Origin S
* @param asset_ Address for the asset
* @param amount_ Amount of the asset to transfer
*/
function transferToken(address asset_, uint256 amount_)
external
onlyGovernor
{
require(asset_ != address(asset()), "Cannot collect OS");
IERC20(asset_).safeTransfer(governor(), amount_);
}
}
50 changes: 12 additions & 38 deletions contracts/contracts/token/WrappedOusd.sol
Original file line number Diff line number Diff line change
@@ -1,36 +1,23 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { ERC4626 } from "../../lib/openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { Governable } from "../governance/Governable.sol";
import { Initializable } from "../utils/Initializable.sol";
import { OUSD } from "./OUSD.sol";
import { WOETH } from "./WOETH.sol";

contract WrappedOusd is ERC4626, Governable, Initializable {
using SafeERC20 for IERC20;

constructor(
ERC20 underlying_,
string memory name_,
string memory symbol_
) ERC20(name_, symbol_) ERC4626(underlying_) Governable() {}

/**
* @notice Enable OUSD rebasing for this contract
*/
function initialize() external onlyGovernor initializer {
OUSD(address(asset())).rebaseOptIn();
}
/**
* @title Wrapped OUSD Token Contract
* @author Origin Protocol Inc
*/
contract WrappedOusd is WOETH {
constructor(ERC20 underlying_) WOETH(underlying_) {}

function name()
public
view
override(ERC20, IERC20Metadata)
virtual
override(WOETH)
returns (string memory)
{
return "Wrapped OUSD";
Expand All @@ -39,23 +26,10 @@ contract WrappedOusd is ERC4626, Governable, Initializable {
function symbol()
public
view
override(ERC20, IERC20Metadata)
virtual
override(WOETH)
returns (string memory)
{
return "WOUSD";
}

/**
* @notice Transfer token to governor. Intended for recovering tokens stuck in
* contract, i.e. mistaken sends. Cannot transfer OUSD
* @param asset_ Address for the asset
* @param amount_ Amount of the asset to transfer
*/
function transferToken(address asset_, uint256 amount_)
external
onlyGovernor
{
require(asset_ != address(asset()), "Cannot collect OUSD");
IERC20(asset_).safeTransfer(governor(), amount_);
}
}
23 changes: 21 additions & 2 deletions contracts/deploy/deployActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1222,8 +1222,6 @@ const deployWOusd = async () => {
const ousd = await ethers.getContract("OUSDProxy");
const dWrappedOusdImpl = await deployWithConfirmation("WrappedOusd", [
ousd.address,
"Wrapped OUSD IMPL",
"WOUSD IMPL",
]);
await deployWithConfirmation("WrappedOUSDProxy");
const wousdProxy = await ethers.getContract("WrappedOUSDProxy");
Expand All @@ -1237,6 +1235,26 @@ const deployWOusd = async () => {
](dWrappedOusdImpl.address, governorAddr, initData);
};

const deployWOeth = async () => {
const { deployerAddr, governorAddr } = await getNamedAccounts();
const sDeployer = await ethers.provider.getSigner(deployerAddr);

const oeth = await ethers.getContract("OETHProxy");
const dWrappedOethImpl = await deployWithConfirmation("WOETH", [
oeth.address,
]);
await deployWithConfirmation("WOETHProxy");
const woethProxy = await ethers.getContract("WOETHProxy");
const woeth = await ethers.getContractAt("WOETH", woethProxy.address);

const initData = woeth.interface.encodeFunctionData("initialize()", []);

await woethProxy.connect(sDeployer)[
// eslint-disable-next-line no-unexpected-multiline
"initialize(address,address,bytes)"
](dWrappedOethImpl.address, governorAddr, initData);
};

const deployOETHSwapper = async () => {
const { deployerAddr, governorAddr } = await getNamedAccounts();
const sDeployer = await ethers.provider.getSigner(deployerAddr);
Expand Down Expand Up @@ -1391,6 +1409,7 @@ module.exports = {
deployUniswapV3Pool,
deployVaultValueChecker,
deployWOusd,
deployWOeth,
deployOETHSwapper,
deployOUSDSwapper,
upgradeNativeStakingSSVStrategy,
Expand Down
2 changes: 2 additions & 0 deletions contracts/deploy/mainnet/001_core.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
deployUniswapV3Pool,
deployVaultValueChecker,
deployWOusd,
deployWOeth,
deployOETHSwapper,
deployOUSDSwapper,
} = require("../deployActions");
Expand All @@ -42,6 +43,7 @@ const main = async () => {
await deployUniswapV3Pool();
await deployVaultValueChecker();
await deployWOusd();
await deployWOeth();
await deployOETHSwapper();
await deployOUSDSwapper();
console.log("001_core deploy done.");
Expand Down
Loading
Loading